pax_global_header00006660000000000000000000000064147604446040014523gustar00rootroot0000000000000052 comment=43f75817e1038cc988bd31e97bbbf33755c504a4 go-sev-guest-0.13.0/000077500000000000000000000000001476044460400141315ustar00rootroot00000000000000go-sev-guest-0.13.0/.github/000077500000000000000000000000001476044460400154715ustar00rootroot00000000000000go-sev-guest-0.13.0/.github/workflows/000077500000000000000000000000001476044460400175265ustar00rootroot00000000000000go-sev-guest-0.13.0/.github/workflows/ci.yml000066400000000000000000000060331476044460400206460ustar00rootroot00000000000000# # Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. # name: CI on: push: tags: - v* branches: [main] pull_request: branches: [main] jobs: build: strategy: matrix: go-version: [1.21.x, 1.22.x] os: [macos-latest, ubuntu-latest] name: Build/Test (${{ matrix.os}}, Go ${{ matrix.go-version }}) runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Install Protoc uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} version: "27.2" - name: Install protoc-gen-go run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 - name: Check Protobuf Generation run: | go generate ./... git diff -G'^[^/]' --exit-code - name: Generate all protobufs run: go generate ./... - name: Build all packages run: go build -v ./... - name: Test all packages run: go test -v ./... - name: Run Go Vet run: go vet ./... lint: strategy: matrix: go-version: [1.21.x] os: [ubuntu-latest] name: Lint ${{ matrix.dir }} (${{ matrix.os }}, Go ${{ matrix.go-version }}) runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Run golangci-lint uses: golangci/golangci-lint-action@v3.6.0 with: version: latest working-directory: ./ args: > -D errcheck -E stylecheck -E goimports -E misspell -E revive -E gofmt -E goimports --out-format=colored-line-number --exclude-use-default=false --max-same-issues=0 --max-issues-per-linter=0 --timeout 2m lintc: strategy: matrix: go-version: [1.21.x] os: [ubuntu-latest] name: Lint CGO (${{ matrix.os}}, Go ${{ matrix.go-version }}) runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Check for CGO Warnings (gcc) run: CGO_CFLAGS=-Werror CC=gcc go build ./... - name: Check for CGO Warnings (clang) run: CGO_CFLAGS=-Werror CC=clang go build ./... go-sev-guest-0.13.0/.github/workflows/release.yml000066400000000000000000000021321476044460400216670ustar00rootroot00000000000000name: release on: push: branches: tags: - 'v*' pull_request: jobs: release: strategy: matrix: go-version: [1.19.x] os: [ubuntu-latest] name: Release for (${{ matrix.os}}, Go ${{ matrix.go-version }}) runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} cache: true - shell: bash run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - id: cache uses: actions/cache@v3 with: path: dist/${{ matrix.os }} key: ${{ matrix.go }}-${{ env.sha_short }} - name: Build all packages run: go build -v ./... - name: Run GoReleaser uses: goreleaser/goreleaser-action@v3 if: success() && startsWith(github.ref, 'refs/tags/') && steps.cache.outputs.cache-hit != 'true' with: version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} go-sev-guest-0.13.0/.gitignore000066400000000000000000000000521476044460400161160ustar00rootroot00000000000000* !*.* !*/ *~ external/* kdsdatabase.bin* go-sev-guest-0.13.0/.goreleaser.yaml000066400000000000000000000021271476044460400172250ustar00rootroot00000000000000builds: - env: - CGO_ENABLED=0 goos: - linux # - windows # - darwin goarch: - amd64 id: "attest" main: ./tools/attest/attest.go binary: attest - env: - CGO_ENABLED=0 goos: - linux # - windows # - darwin goarch: - amd64 id: "check" main: ./tools/check/check.go binary: check archives: - format: tar.gz # this name template makes the OS and Arch compatible with the results of uname. name_template: >- {{ .ProjectName }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }} # use zip for windows archives format_overrides: - goos: windows format: zip checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ incpatch .Version }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' # yaml-language-server: $schema=https://goreleaser.com/static/schema.json # vim: set ts=2 sw=2 tw=0 fo=cnqoj go-sev-guest-0.13.0/CONTRIBUTING.md000066400000000000000000000021151476044460400163610ustar00rootroot00000000000000# How to Contribute We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ## Community Guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). go-sev-guest-0.13.0/INSTALL.md000066400000000000000000000025511476044460400155640ustar00rootroot00000000000000# Installation instructions go-sev-guest is a library and set of CLI tools for interacting with the `/dev/sev-guest` driver. There are thus a few requirements for its use. ## System requirements This driver is only available on AMD SEV-SNP enabled virtual machines. Do determine support, run ```shell dmesg | grep "SEV-SNP supported" ``` Your Linux distribution may build in support for the sev-guest driver, or may relegate it to a loadable kernel module. Ensure the module is loaded with ```shell modprobe sev-guest ``` If this command fails, check with your distribution for which installable package it may be distributed in, and install that. For example, Ubuntu may distribute `sev-guest` in `linux-modules-$(uname -r)`. ## Kernel config When building your own Linux kernel, on top of the other configuration options needed for SEV-SNP, you will need to have `CONFIG_VIRT_DRIVERS=y` and either `CONFIG_SEV_GUEST=y` or `CONFIG_SEV_GUEST=m` depending on whether you want the driver to be built in or a module. ## Device requires root permissions Unless your image has custom initialization rules to grant broader privileges to the sev-guest device, the Linux user that accesses `/dev/sev-guest` must have root privileges. To provide attestation report capabilities to a lesser-privileged user, you will need to create a privileged client that can act on their behalf. go-sev-guest-0.13.0/LICENSE000066400000000000000000000261361476044460400151460ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. go-sev-guest-0.13.0/LIMITATIONS.md000066400000000000000000000072151476044460400162540ustar00rootroot00000000000000# Security limitations This document's goal is to provide a short discussion on what security properties access to /dev/sev-guest has, specifically in a Cloud setting. ## Initial assumptions For claims in this document, we assume that the VM firmware is vendored and signed by the vendor, or not signed at all. This will be the common use case. If a virtual machine monitor gives you access to provide your own launch image and signed IDBlock, then you have full control and responsibility over what that image does, and how careful it is to give workloads access to the SEV device. Since this library is for wrapping the Linux driver /dev/sev-guest which will be booted from a UEFI firmware, further discussion of other uses of SEV-SNP are out of scope. ## Implications of a vendored firmware Your Cloud service provider (CSP) may launch SEV-SNP VMs with their own build of UEFI firmware. This build and its signature in IDBlock are probably widely deployed across the CSP's fleet. Because the firmware and IDBlock are the same everywhere, the measurement in the attestation report will be the same everywhere. The IDBlock, if provided, may also be the same everywhere. The security of an attestation measurement or a derived key are proportional to the specificity of the initially measured image. If the initial image and its IDBlock is available to everyone and can run any workload, then keys bound to their measurement are known to everyone. An IDBlock signed by a different key will lead to different keys, but the IDBlock and ID Auth are also not secret in the SEV-SNP threat model. The measured image's behavior in granting authorization is what is important. At the moment, the basis for most VM firmware, Open Virtual Machine Firmware (OVMF), does not have the behavior to lock in such a specific measurement and authorization semantics in a way that is reflected in the SEV-SNP attestation report. Measured boot integrity is dynamic post-launch via the TPM 2.0 specification, which for VMs is virtualized in software and not secret within the SEV-SNP threat model. Supposing we did have a boot stack that accounted for workload and its configuration in the SEV-SNP attestation report, software updates change the measurement and thus change the derived keys. In a self-updating VM, you'd need custom software to manage the implications of changing derived keys. ## MSG_KEY_REQ, or GetDerivedKey Keys are derived from the launch information discussed above and the specific machine's SEV-SNP key called the Versioned Chip Endorsement Key (VCEK). When you do not have full control over the machine that derives the key, and your launch image isn't fully linked to the workload you trust to have access to the key, you should not set `UseVCEK` to true. With `UseVCEK` set to false, you must be using an image that supports a migration agent (MA). The MA will register a root key that migrates with the image, called the VMRK. The security of this VMRK is entirely up to the MA's logic. If the key is meant to persist across full shutdown and restart, then you have to solve a hard problem: sealing that key to persist in a way that only the authorized workload should later have access to. That is the same problem that exists for VCEK. If you're okay with keys that migrate but aren't otherwise recoverable, then VMRK key-based derivation should meet your needs. To many, that possibility of unrecoverable data loss is too risky to choose this option either. Because of the danger in both root key selections, we do not recommend using this command unless you have full ownership of and secure physical access to the machine that will run it, and trust all parties that run software on that machine. go-sev-guest-0.13.0/README.md000066400000000000000000000232561476044460400154200ustar00rootroot00000000000000# SEV Guest This project offers libraries for a simple wrapper around the `/dev/sev-guest` device in Linux, as well as a library for attestation verification of fundamental components of an attestation report. This project is split into two complementary roles. The first role is producing an attestation report, and the second is checking an attestation report. The `client` library produces reports, the `verify` library verifies reports' signatures and key certificates, and the `validate` library checks non-signature report fields against a user-provided policy. ## `client` This library should be used within the confidential workload to collect an attestation report along with requisite certificates. Your main interactions with it will be to open the device, get an attestation report with your provided 64 bytes of user data (typically a nonce or a hash of a public key), and then close the device. For convenience, the attestation with its associated certificates can be collected in a wire-transmittable protocol buffer format. ### `func OpenDevice() (*LinuxDevice, error)` This function creates a file descriptor to the `/dev/sev-guest` device and returns an object that has methods encapsulating commands to the device. When done, remember to `Close()` the device. ### `func GetExtendedReport(d Device, reportData [64]byte) (*pb.Attestation, error)` This function takes an object implementing the `Device` interface (e.g., a `LinuxDevice`) and returns the protocol buffer representation of the attestation report and associated certificates. The report will be associated with VM privilege level 0. You can provide a different privilege level as the third argument to `GetExtendedReportAtVmpl`. You can use `GetRawExtendedReport` or `GetRawExtendedReportAtVmpl` to get the AMD SEV-SNP API formatted report and certificate table, or just `GetReport`, `GetReportAtVmpl`, `GetRawReport`, or `GetRawReportAtVmpl` to avoid fetching the certificate table. ### `func GetDerivedKeyAcknowledgingItsLimitations(d Device, request *SnpDerivedKeyReq) ([]byte, error)` This function uses the `/dev/sev-guest` command for requesting a key derived from data that is measured at VM launch time, with the additional ability to continue to generate the same key as at earlier TCB and GuestSVN values. This function's name is selected to discourage its use in a Cloud setting. See [LIMITATIONS.md](LIMITATIONS.md). ### `func (d Device) Close() error` Closes the device. ### `func (d Device) Product() *spb.SevProduct` Returns a representation of CPU info relevant to the AMD SEV product version. ## `verify` This library will check the signature and basic well-formedness properties of an attestation report and certificate chain. The requirements for report well-formedness comes from the AMD SEV-SNP API specification, and the requirements for certificate well-formedness come from the AMD Key Distribution Service (KDS) specification. This library embeds AMD's root and SEV intermediate keys' certificates ([AMD source](https://download.amd.com/developer/eula/sev/ask_ark_milan.cert)) for the [KDS product_name=Milan cert_chain](https://kdsintf.amd.com/vcek/v1/Milan/cert_chain) in the AMD SEV certificate format to cross check against any certificate chain that it's sent. The SEV certificate format is defined in an appendix of the AMD SEV API specification. ### `func SnpAttestation(attestation *spb.Attestation, options *Options) error` This function verifies that the attestation has a valid signature and certificate chain, and optionally checks the certificate revocation list (CRL). At time of writing, the CRL is empty. From discussions with AMD, we expect the CRL to never contain a VCEK or ARK, and only in a very rare circumstance contain the ASK (intermediate signing key). The default option is to not check the CRL. Example expected invocation: ``` verify.SnpAttestation(myAttestation, verify.DefaultOptions()) ``` #### `Options` type This type contains three fields: * `CheckRevocations bool`: if true, then `SnpAttestation` will download the certificate revocation list (CRL) and check for revocations. * `Getter HTTPSGetter`: must be non-`nil` if `CheckRevocations` is true. * `TrustedRoots map[string][]*AMDRootCerts`: if `nil`, uses the library's embedded certificates. Maps a product name to all allowed root certifications for that product (e.g., Milan). The `HTTPSGetter` interface consists of a single method `Get(url string) ([]byte, error)` that should return the body of the HTTPS response. #### `AMDRootCerts` type This type has 6 fields, the first 3 of which are mandatory: * `Product string`: the name of the product this bundle is for (e.g., `"Milan"`). * `AskX509 *x509.Certificate`: an X.509 representation of the AMD SEV Signer intermediate key (ASK)'s certificate. * `ArkX509 *x509.Certificate`: an X.509 representation of the AMD SEV Root key (ARK)'s certificate. * `AskSev *abi.AskCert`: if non-`nil`, will cross-check with `AskX509`. Represents the information present in the AMD SEV certificate format for the ASK. * `ArkSev *abi.AskCert`: if non-`nil`, will cross-check with `ArkX509`. Represents the information present in the AMD SEV certificate format for the ARK. * `CRL *x509.RevocationList`: the certificate revocation list signed by the ARK. Will be populated if `SnpAttestation` is called with `CheckRevocations: true`. ## `validate` This library checks fields of an attestation report according to a policy provided by the user. The policy is represented with the `Options` type, which specifies which fields to check, what their exact values should be, or whether the field should be within a given bound. ### `func SnpAttestation(attestation *spb.Attestation, options *Options) error` Not to be confused with `verify.SnpAttestation` for checking certificates and signatures, the `validate.SnpAttestation` function is more open-ended about what reports are acceptable. It's up to the user of the library to set the parameters of acceptable values with the `options` argument. #### The `Option` type An instance of the `Option` type is a simple validation policy for non-signature fields of an attestation report. The fields that either can be skipped or must match the given value exactly are: * `ReportData` for the `REPORT_DATA` field * `HostData` for the `HOST_DATA` field * `ImageID` for the `IMAGE_ID` field * `FamilyID` for the `FAMILY_ID` field * `ReportID` for the `FEPORT_ID` field * `ReportIDMA` for the `REPORT_ID_MA` field * `Measurement` for the `MEASUREMENT` field The fields that provide a minimum acceptable value are: * `MinimumBuild` for the minimum build number for the AMD secure processor firmware. * `RequireAuthorKey` for whether `AUTHOR_KEY_EN` can be 0 or 1 (false), or just 1 (true). * `RequireIDBlock` for whether IDBlock fields can be anything (false) or must validate (true) against the `Trusted` family of options. The fields that provide a maximum acceptable value are: * `GuestPolicy`: each true field of `GuestPolicy` is permission for an attestation report's `POLICY` corresponding bit to be set. * `PermitProvisionalFirmware`: if false, the minimum TCB and API values are equal to the reported values. If true, the maximum TCB and API values are the reported values. * `PlatformInfo`: each true field of `PlatformInfo` is permission for the attestation report's `PLATFORM_INFO` corresponding bit to be set. Finally, the fields for trusting IDBlock signers. Both ID keys and Author keys have x.509 certificate and SEV-SNP hash format inputs for usability. The x.509 certificates will be converted to their corresponding SEV-SNP API format and appended to the corresponding `KeyHashes` array. Only certificates for ECSDA P-384 public keys are considered. All other certificates are quietly ignored. * `TrustedAuthorKeys`: x.509 certificates for author keys that are trusted to endorse an attestation report. If the report's author key is trusted, then the identity key it signed is implicitly trusted. * `TrustedAuthorKeyHashes`: An array of SHA-384 hashes of the SEV-SNP API format for an ECDSA public key. Has the same validation behavior as `TrustedAuthorKeys`. * `TrustedIDKeys`: x.509 certificates for identity keys that are trusted to endorse an attestation report. If the report's identity key is explicitly trusted, then its author key does not need to be trusted. * `TrustedIDKeyHashes`: An array of SHA-384 hashes of the SEV-SNP API format for an ECDSA public key. Has the same validation behavior as `TrustedIDKeys`. * `Product`: A replacement or supplemental `SevProduct` value to use for a given attestation or report. If nil, uses the information present in the attestation proto, or provides a default `Milan-B0` value. ## License go-sev-guest is released under the Apache 2.0 license. ``` Copyright 2022 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ## Links * [AMD SEV API specification](https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf) * [AMD SEV-SNP API specification](https://www.amd.com/system/files/TechDocs/56860.pdf) * [AMD KDS specification](https://www.amd.com/system/files/TechDocs/57230.pdf) ## Disclaimers This is not an officially supported Google product. go-sev-guest-0.13.0/abi/000077500000000000000000000000001476044460400146645ustar00rootroot00000000000000go-sev-guest-0.13.0/abi/abi.go000066400000000000000000001171701476044460400157550ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package abi encapsulates types and status codes from the AMD-SP (AKA PSP) device. package abi import ( "crypto/ecdsa" "encoding/binary" "encoding/hex" "fmt" "math/big" pb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/logger" "github.com/google/uuid" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" "google.golang.org/protobuf/types/known/wrapperspb" ) const ( // AeadAes256Gcm is the SNP API value for the AES-256-GCM encryption algorithm. AeadAes256Gcm = 1 // SignEcdsaP384Sha384 is the SNP API value for the ECC+SHA signing algorithm. SignEcdsaP384Sha384 = 1 // EccP384 is the SNP API value for the P-384 ECC curve identifier. EccP384 = 2 // ReportSize is the ABI-specified byte size of an SEV-SNP attestation report. ReportSize = 0x4A0 // FamilyIDSize is the field size of FAMILY_ID in an SEV-SNP attestation report. FamilyIDSize = 16 // ImageIDSize is the field size of IMAGE_ID in an SEV-SNP attestation report. ImageIDSize = 16 // ReportDataSize is the field size of REPORT_DATA in an SEV-SNP attestation report. ReportDataSize = 64 // MeasurementSize is the field size of MEASUREMENT in an SEV-SNP attestation report. MeasurementSize = 48 // HostDataSize is the field size of HOST_DATA in an SEV-SNP attestation report. HostDataSize = 32 // IDKeyDigestSize is the field size of ID_KEY_DIGEST in an SEV-SNP attestation report. IDKeyDigestSize = 48 // AuthorKeyDigestSize is the field size of AUTHOR_KEY_DIGEST in an SEV-SNP attestation report. AuthorKeyDigestSize = 48 // ReportIDSize is the field size of REPORT_ID in an SEV-SNP attestation report. ReportIDSize = 32 // ReportIDMASize is the field size of REPORT_ID_MA in an SEV-SNP attestation report. ReportIDMASize = 32 // ChipIDSize is the field size of CHIP_ID in an SEV-SNP attestation report. ChipIDSize = 64 // SignatureSize is the field size of SIGNATURE in an SEV-SNP attestation report. SignatureSize = 512 policyOffset = 0x08 policySMTBit = 16 policyReserved1bit = 17 policyMigrateMABit = 18 policyDebugBit = 19 policySingleSocketBit = 20 maxPlatformInfoBit = 5 signatureOffset = 0x2A0 ecdsaRSsize = 72 // From the ECDSA-P384-SHA384 format in SEV SNP API specification. // From the ECDSA public key format in SEV SNP API specification. ecdsaQXoffset = 0x04 ecdsaQYoffset = 0x4c ecdsaQYend = 0x94 // EcdsaP384Sha384SignatureSize is the length in bytes of the ECDSA-P384-SHA384 signature format. EcdsaP384Sha384SignatureSize = ecdsaRSsize + ecdsaRSsize // EcsdaPublicKeySize is the length in bytes of the Curve, QX, QY elliptic curve public key // representation in the AMD SEV ABI. EcsdaPublicKeySize = 0x404 // CertTableEntrySize is the ABI size of the certificate table entry struct. CertTableEntrySize = 24 // GUIDSize is the byte length of a GUID's binary representation. GUIDSize = 16 // The following GUIDs are defined by the AMD Guest-host communication block specification // for MSG_REPORT_REQ: // https://www.amd.com/system/files/TechDocs/56421-guest-hypervisor-communication-block-standardization.pdf // VcekGUID is the Versioned Chip Endorsement Key GUID VcekGUID = "63da758d-e664-4564-adc5-f4b93be8accd" // VlekGUID is the Versioned Loaded Endorsement Key GUID VlekGUID = "a8074bc2-a25a-483e-aae6-39c045a0b8a1" // AskGUID is the AMD signing Key GUID. Used for the ASVK as well. AskGUID = "4ab7b379-bbac-4fe4-a02f-05aef327c782" // ArkGUID is the AMD Root Key GUID ArkGUID = "c0b406a4-a803-4952-9743-3fb6014cd0ae" // AsvkGUID may not be defined, but we'd like it to be, so that // a single machine can use both VCEK and VLEK report signing. AsvkGUID = "00000000-0000-0000-0000-000000000000" // ExtraPlatformInfoGUID represents more information about the machine collecting an attestation // report than just the report to help interpret the attestation report. ExtraPlatformInfoGUID = "ecae0c0f-9502-43b1-afa2-0ae2e0d565b6" // ExtraPlatformInfoV0Size is the minimum size for an ExtraPlatformInfo blob. ExtraPlatformInfoV0Size = 8 // CpuidProductMask keeps only the SevProduct-relevant bits from the CPUID(1).EAX result. CpuidProductMask = 0x0fff0fff extendedFamilyShift = 20 extendedModelShift = 16 familyShift = 8 modelShift = 4 // Combined extended values zen3zen4Family = 0x19 zen5Family = 0x1A milanModel = 0 | 1 genoaModel = (1 << 4) | 1 turinModel = 2 // ReportVersion2 is set by the SNP API specification // https://web.archive.org/web/20231222054111if_/http://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf ReportVersion2 = 2 // MinSupportedReportVersion is the lowest attestation report version that this library supports. MinSupportedReportVersion = 2 // ReportVersion3 is set by the SNP API specification // https://www.amd.com/system/files/TechDocs/56860.pdf ReportVersion3 = 3 // MaxSupportedReportVersion is the highest attestation report version that this library supports. MaxSupportedReportVersion = 4 ) // CertTableHeaderEntry defines an entry of the beginning of an extended attestation report which // points to a specific key's certificate. type CertTableHeaderEntry struct { // GUID is one of VcekGUID, AskGUID, or ArkGUID to identify which key an offset/length corresponds // to. GUID uuid.UUID // Offset is the offset into the data pages passed to the extended get_report where the specified // key's certificate resides. Offset uint32 // Length is the length of the certificate within the data pages. Length uint32 } // CertTableEntry represents both the GUID and whole Certificate contents denoted by the // CertTableHeaderEntry ABI struct. type CertTableEntry struct { GUID uuid.UUID RawCert []byte } // CertTable represents each (GUID, Blob) pair of certificates returned by an extended guest // request. type CertTable struct { Entries []CertTableEntry } // Appendix B.1 of the SEV API specification // AskCert is the SEV format for AMD signing key certificates. type AskCert struct { Version uint32 KeyID uuid.UUID CertifyingID uuid.UUID // Equals KeyID if self-signed. KeyUsage uint32 // Table 111: 00 == Root signing key, 0x13 == SEV signing key. PubExpSize uint32 // Must be 2048 or 4096 ModulusSize uint32 // Must be 2048 or 4096 PubExp []byte Modulus []byte Signature []byte } // SnpPlatformInfo represents an interpretation of the PLATFORM_INFO field of an attestation report. type SnpPlatformInfo struct { // SMTEnabled represents if the platform that produced the attestation report has SMT enabled. SMTEnabled bool // TSMEEnabled represents if the platform that produced the attestation report has transparent // secure memory encryption (TSME) enabled. TSMEEnabled bool // ECCEnabled indicates that the platform is using error correcting codes for memory. // Present when EccMemReporting feature bit is set. ECCEnabled bool // RAPLDisabled indicates that the RAPL is disabled. RAPLDisabled bool // CiphertextHidingDRAMEnabled indicates cypher text hiding is enabled for DRAM. CiphertextHidingDRAMEnabled bool // AliasCheckComplete indicates that alias detection has completed since the last system reset and there are no aliasing addresses. // Mitigation for https://badram.eu/, see https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3015.html#mitigation. AliasCheckComplete bool } // SnpPolicy represents the bitmask guest policy that governs the VM's behavior from launch. type SnpPolicy struct { // ABIMajor is the minimum SEV SNP ABI version needed to run the guest's minor version number. ABIMinor uint8 // ABIMajor is the minimum SEV SNP ABI version needed to run the guest's major version number. ABIMajor uint8 // SMT is true if symmetric multithreading is allowed. SMT bool // MigrateMA is true if the guest is allowed to have a migration agent. MigrateMA bool // Debug is true if the VM can be decrypted by the host for debugging purposes. Debug bool // SingleSocket is true if the guest may only be active on a single socket. SingleSocket bool } // ParseSnpPolicy interprets the SEV SNP API's guest policy bitmask into an SnpPolicy struct type. func ParseSnpPolicy(guestPolicy uint64) (SnpPolicy, error) { result := SnpPolicy{} if guestPolicy&uint64(1<> 8) & 0xff) result.SMT = (guestPolicy & (1 << policySMTBit)) != 0 result.MigrateMA = (guestPolicy & (1 << policyMigrateMABit)) != 0 result.Debug = (guestPolicy & (1 << policyDebugBit)) != 0 result.SingleSocket = (guestPolicy & (1 << policySingleSocketBit)) != 0 return result, nil } // SnpPolicyToBytes translates a structural representation of a valid SNP policy to its ABI format. func SnpPolicyToBytes(policy SnpPolicy) uint64 { result := uint64(policy.ABIMinor) | uint64(policy.ABIMajor)<<8 | uint64(1<>lo)&((1<<(hi-lo+1))-1) != 0 { return fmt.Errorf("mbz range %s[0x%x:0x%x] not all zero: %x", base, lo, hi, data) } return nil } // ReportToSignatureDER returns the signature component of an attestation report in DER format for // use in x509 verification. func ReportToSignatureDER(report []byte) ([]byte, error) { if len(report) != ReportSize { return nil, fmt.Errorf("incorrect report size: %x, want %x", len(report), ReportSize) } algo := SignatureAlgo(report) if algo != SignEcdsaP384Sha384 { return nil, fmt.Errorf("unknown signature algorithm: %d", algo) } signature := report[signatureOffset:ReportSize] var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(AmdBigInt(ecdsaGetR(signature))) b.AddASN1BigInt(AmdBigInt(ecdsaGetS(signature))) }) return b.Bytes() } func ecdsaGetR(signature []byte) []byte { return signature[0x0:0x48] } func ecdsaGetS(signature []byte) []byte { return signature[0x48:0x90] } func clone(b []byte) []byte { result := make([]byte, len(b)) copy(result, b) return result } func signatureAlgoSlice(report []byte) []byte { return report[0x34:0x38] } // SignatureAlgo returns the SignatureAlgo field of a raw SEV-SNP attestation report. func SignatureAlgo(report []byte) uint32 { return binary.LittleEndian.Uint32(signatureAlgoSlice(report)) } // ReportSigner represents which kind of key is expected to have signed the attestation report type ReportSigner uint8 const ( // VcekReportSigner is the SIGNING_KEY value for if the VCEK signed the attestation report. VcekReportSigner ReportSigner = iota // VlekReportSigner is the SIGNING_KEY value for if the VLEK signed the attestation report. VlekReportSigner endorseReserved2 endorseReserved3 endorseReserved4 endorseReserved5 endorseReserved6 // NoneReportSigner is the SIGNING_KEY value for if the attestation report is not signed. NoneReportSigner ) // SignerInfo represents information about the signing circumstances for the attestation report. type SignerInfo struct { // SigningKey represents kind of key by which a report was signed. SigningKey ReportSigner // MaskChipKey is true if the host chose to enable CHIP_ID masking, to cause the report's CHIP_ID // to be all zeros. MaskChipKey bool // AuthorKeyEn is true if the VM is launched with an IDBLOCK that includes an author key. AuthorKeyEn bool } // String returns a ReportSigner string rendering. func (k ReportSigner) String() string { switch k { case VcekReportSigner: return "VCEK" case VlekReportSigner: return "VLEK" case NoneReportSigner: return "None" default: return fmt.Sprintf("UNKNOWN(%d)", byte(k)) } } // ParseSignerInfo interprets report[0x48:0x4c] into its component pieces and errors // on non-zero mbz fields. func ParseSignerInfo(signerInfo uint32) (result SignerInfo, err error) { info64 := uint64(signerInfo) if err = mbz64(info64, "data[0x48:0x4C]", 31, 5); err != nil { return result, err } result.SigningKey = ReportSigner((signerInfo >> 2) & 7) if result.SigningKey > VlekReportSigner && result.SigningKey < NoneReportSigner { return result, fmt.Errorf("signing_key values 2-6 are reserved. Got %v", result.SigningKey) } result.MaskChipKey = (signerInfo & 2) != 0 result.AuthorKeyEn = (signerInfo & 1) != 0 return result, nil } // ComposeSignerInfo returns the uint32 value expected to populate the attestation report byte range // 0x48:0x4C. func ComposeSignerInfo(signerInfo SignerInfo) uint32 { var result uint32 if signerInfo.AuthorKeyEn { result |= 1 } if signerInfo.MaskChipKey { result |= 2 } result |= uint32(signerInfo.SigningKey) << 2 return result } // ReportSignerInfo returns the signer info component of a SEV-SNP raw report. func ReportSignerInfo(data []byte) (uint32, error) { if len(data) < 0x4C { return 0, fmt.Errorf("report too small: %d", len(data)) } return binary.LittleEndian.Uint32(data[0x48:0x4C]), nil } // ReportToProto creates a pb.Report from the little-endian AMD SEV-SNP attestation report byte // array in SEV SNP ABI format for ATTESTATION_REPORT. func ReportToProto(data []uint8) (*pb.Report, error) { if len(data) < ReportSize { return nil, fmt.Errorf("array size is 0x%x, an SEV-SNP attestation report size is 0x%x", len(data), ReportSize) } r := &pb.Report{} // r.Version should be 2, but that's left to validation step. r.Version = binary.LittleEndian.Uint32(data[0x00:0x04]) r.GuestSvn = binary.LittleEndian.Uint32(data[0x04:0x08]) r.Policy = binary.LittleEndian.Uint64(data[0x08:0x10]) if _, err := ParseSnpPolicy(r.Policy); err != nil { return nil, fmt.Errorf("malformed guest policy: %v", err) } r.FamilyId = clone(data[0x10:0x20]) r.ImageId = clone(data[0x20:0x30]) r.Vmpl = binary.LittleEndian.Uint32(data[0x30:0x34]) r.SignatureAlgo = SignatureAlgo(data) r.CurrentTcb = binary.LittleEndian.Uint64(data[0x38:0x40]) r.PlatformInfo = binary.LittleEndian.Uint64(data[0x40:0x48]) signerInfo, err := ParseSignerInfo(binary.LittleEndian.Uint32(data[0x48:0x4C])) if err != nil { return nil, err } r.SignerInfo = ComposeSignerInfo(signerInfo) if err := mbz(data, 0x4C, 0x50); err != nil { return nil, err } r.ReportData = clone(data[0x50:0x90]) r.Measurement = clone(data[0x90:0xC0]) r.HostData = clone(data[0xC0:0xE0]) r.IdKeyDigest = clone(data[0xE0:0x110]) r.AuthorKeyDigest = clone(data[0x110:0x140]) r.ReportId = clone(data[0x140:0x160]) r.ReportIdMa = clone(data[0x160:0x180]) r.ReportedTcb = binary.LittleEndian.Uint64(data[0x180:0x188]) mbzLo := 0x188 if r.Version >= ReportVersion3 { mbzLo = 0x18B r.Cpuid1EaxFms = FmsToCpuid1Eax(data[0x188], data[0x189], data[0x18A]) } if err := mbz(data, mbzLo, 0x1A0); err != nil { return nil, err } r.ChipId = clone(data[0x1A0:0x1E0]) r.CommittedTcb = binary.LittleEndian.Uint64(data[0x1E0:0x1E8]) r.CurrentBuild = uint32(data[0x1E8]) r.CurrentMinor = uint32(data[0x1E9]) r.CurrentMajor = uint32(data[0x1EA]) if err := mbz(data, 0x1EB, 0x1EC); err != nil { return nil, err } r.CommittedBuild = uint32(data[0x1EC]) r.CommittedMinor = uint32(data[0x1ED]) r.CommittedMajor = uint32(data[0x1EE]) if err := mbz(data, 0x1EF, 0x1F0); err != nil { return nil, err } r.LaunchTcb = binary.LittleEndian.Uint64(data[0x1F0:0x1F8]) if err := mbz(data, 0x1F8, signatureOffset); err != nil { return nil, err } if r.SignatureAlgo == SignEcdsaP384Sha384 { if err := mbz(data, signatureOffset+EcdsaP384Sha384SignatureSize, ReportSize); err != nil { return nil, err } } r.Signature = clone(data[signatureOffset:ReportSize]) return r, nil } // ReportCertsToProto creates a pb.Attestation from the report and certificate table represented in // data. The report is expected to take exactly abi.ReportSize bytes, followed by the certificate // table. func ReportCertsToProto(data []uint8) (*pb.Attestation, error) { var certs []uint8 report := data if len(data) >= ReportSize { report = data[:ReportSize] certs = data[ReportSize:] } mreport, err := ReportToProto(report) if err != nil { return nil, err } table := new(CertTable) if err := table.Unmarshal(certs); err != nil { return nil, err } return &pb.Attestation{Report: mreport, CertificateChain: table.Proto()}, nil } func checkReportSizes(r *pb.Report) error { if len(r.FamilyId) != FamilyIDSize { return fmt.Errorf("report family_id length is %d, expect %d", len(r.FamilyId), FamilyIDSize) } if len(r.ImageId) != ImageIDSize { return fmt.Errorf("report image_id length is %d, expect %d", len(r.ImageId), ImageIDSize) } if len(r.ReportData) != ReportDataSize { return fmt.Errorf("report_data length is %d, expect %d", len(r.ReportData), ReportDataSize) } if len(r.Measurement) != MeasurementSize { return fmt.Errorf("measurement length is %d, expect %d", len(r.Measurement), MeasurementSize) } if len(r.HostData) != HostDataSize { return fmt.Errorf("host_data length is %d, expect %d", len(r.HostData), HostDataSize) } if len(r.IdKeyDigest) != IDKeyDigestSize { return fmt.Errorf("id_key_digest length is %d, expect %d", len(r.IdKeyDigest), IDKeyDigestSize) } if len(r.AuthorKeyDigest) != AuthorKeyDigestSize { return fmt.Errorf("author_key_digest length is %d, expect %d", len(r.AuthorKeyDigest), AuthorKeyDigestSize) } if len(r.ReportId) != ReportIDSize { return fmt.Errorf("report_id length is %d, expect %d", len(r.ReportId), ReportIDSize) } if len(r.ReportIdMa) != ReportIDMASize { return fmt.Errorf("report_id_ma length is %d, expect %d", len(r.ReportIdMa), ReportIDMASize) } if len(r.ChipId) != ChipIDSize { return fmt.Errorf("chip_id length is %d, expect %d", len(r.ChipId), ChipIDSize) } if len(r.Signature) != SignatureSize { return fmt.Errorf("signature length is %d, expect %d", len(r.Signature), SignatureSize) } return nil } // ValidateReportFormat returns an error if the provided buffer violates structural expectations of // attestation report data. func ValidateReportFormat(r []byte) error { if len(r) < ReportSize { return fmt.Errorf("report size is %d bytes. Expected %d bytes", len(r), ReportSize) } version := binary.LittleEndian.Uint32(r[0x00:0x04]) if version < MinSupportedReportVersion || version > MaxSupportedReportVersion { return fmt.Errorf("report version is: %d. Expected between %d and %d", version, MinSupportedReportVersion, MaxSupportedReportVersion) } policy := binary.LittleEndian.Uint64(r[0x08:0x10]) if _, err := ParseSnpPolicy(policy); err != nil { return fmt.Errorf("malformed guest policy: %v", err) } return nil } // ReportToAbiBytes translates the report back into its little-endian ABI format. func ReportToAbiBytes(r *pb.Report) ([]byte, error) { if r == nil { return nil, fmt.Errorf("report is nil") } if err := checkReportSizes(r); err != nil { return nil, err } // Zero-initialized array fills all the reserved fields with the required zeros. data := make([]byte, ReportSize) binary.LittleEndian.PutUint32(data[0x00:0x04], r.Version) binary.LittleEndian.PutUint32(data[0x04:0x08], r.GuestSvn) binary.LittleEndian.PutUint64(data[0x08:0x10], r.Policy) copy(data[0x10:0x20], r.FamilyId[:]) copy(data[0x20:0x30], r.ImageId[:]) binary.LittleEndian.PutUint32(data[0x30:0x34], r.Vmpl) binary.LittleEndian.PutUint32(signatureAlgoSlice(data), r.SignatureAlgo) binary.LittleEndian.PutUint64(data[0x38:0x40], r.CurrentTcb) binary.LittleEndian.PutUint64(data[0x40:0x48], r.PlatformInfo) if _, err := ParseSignerInfo(r.SignerInfo); err != nil { return nil, err } binary.LittleEndian.PutUint32(data[0x48:0x4C], r.SignerInfo) copy(data[0x50:0x90], r.ReportData[:]) copy(data[0x90:0xC0], r.Measurement[:]) copy(data[0xC0:0xE0], r.HostData[:]) copy(data[0xE0:0x110], r.IdKeyDigest[:]) copy(data[0x110:0x140], r.AuthorKeyDigest[:]) copy(data[0x140:0x160], r.ReportId[:]) copy(data[0x160:0x180], r.ReportIdMa[:]) binary.LittleEndian.PutUint64(data[0x180:0x188], r.ReportedTcb) // Add CPUID information if this is a version 3 report. if r.Version >= ReportVersion3 { family, model, stepping := FmsFromCpuid1Eax(r.Cpuid1EaxFms) data[0x188] = family data[0x189] = model data[0x18A] = stepping } copy(data[0x1A0:0x1E0], r.ChipId[:]) binary.LittleEndian.PutUint64(data[0x1E0:0x1E8], r.CommittedTcb) if r.CurrentBuild >= (1 << 8) { return nil, fmt.Errorf("current_build field must fit in a byte, got %d", r.CurrentBuild) } if r.CurrentMinor >= (1 << 8) { return nil, fmt.Errorf("current_minor field must fit in a byte, got %d", r.CurrentMinor) } if r.CurrentMajor >= (1 << 8) { return nil, fmt.Errorf("current_major field must fit in a byte, got %d", r.CurrentMajor) } data[0x1E8] = byte(r.CurrentBuild) data[0x1E9] = byte(r.CurrentMinor) data[0x1EA] = byte(r.CurrentMajor) if r.CommittedBuild >= (1 << 8) { return nil, fmt.Errorf("committed_build field must fit in a byte, got %d", r.CommittedBuild) } if r.CommittedMinor >= (1 << 8) { return nil, fmt.Errorf("committed_minor field must fit in a byte, got %d", r.CommittedMinor) } if r.CommittedMajor >= (1 << 8) { return nil, fmt.Errorf("committed_major field must fit in a byte, got %d", r.CommittedMajor) } data[0x1EC] = byte(r.CommittedBuild) data[0x1ED] = byte(r.CommittedMinor) data[0x1EE] = byte(r.CommittedMajor) binary.LittleEndian.PutUint64(data[0x1F0:0x1F8], r.LaunchTcb) copy(data[signatureOffset:ReportSize], r.Signature[:]) return data, nil } // SignedComponent returns the bytes of the SnpAttestationReport that are signed by the AMD-SP. func SignedComponent(report []byte) []byte { // Table 21 of https://www.amd.com/system/files/TechDocs/56860.pdf shows the signature is over // all bytes prior to the signature in the report. return report[0:signatureOffset] } func reverse(d []byte) []byte { for i := 0; i < len(d)/2; i++ { swapIndex := len(d) - i - 1 tmp := d[i] d[i] = d[swapIndex] d[swapIndex] = tmp } return d } func bigIntToAMDRS(b *big.Int) []byte { var result [ecdsaRSsize]byte b.FillBytes(result[:]) return reverse(result[:]) } // EcdsaPublicKeyToBytes returns the AMD SEV ABI format of the ECDSA P-384 curve public key. func EcdsaPublicKeyToBytes(key *ecdsa.PublicKey) ([]byte, error) { result := make([]byte, EcsdaPublicKeySize) switch key.Curve.Params().Name { case "P-384": binary.LittleEndian.PutUint32(result[0:4], EccP384) default: return nil, fmt.Errorf("ecdsa public key is not on curve P-384") } copy(result[ecdsaQXoffset:ecdsaQYoffset], bigIntToAMDRS(key.X)) copy(result[ecdsaQYoffset:ecdsaQYend], bigIntToAMDRS(key.Y)) return result, nil } // AmdBigInt returns a given AMD format little endian big integer as a big.Int. func AmdBigInt(b []byte) *big.Int { return new(big.Int).SetBytes(reverse(clone(b))) } // SetSignature sets the signature component the SnpAttestationReport with the specified // representation of the R, S components of an ECDSA signature. Useful for testing. func SetSignature(r, s *big.Int, report []byte) error { if len(report) != ReportSize { return fmt.Errorf("unexpected report size: %x, want %x", len(report), ReportSize) } signature := report[signatureOffset:ReportSize] copy(ecdsaGetR(signature), bigIntToAMDRS(r)) copy(ecdsaGetS(signature), bigIntToAMDRS(s)) return nil } // Unmarshal populates a CertTableHeaderEntry from its ABI representation. func (h *CertTableHeaderEntry) Unmarshal(data []byte) error { if len(data) < CertTableEntrySize { return fmt.Errorf("data too small: %v, want %v", len(data), CertTableEntrySize) } copy(h.GUID[:], data[0:GUIDSize]) uint32Size := 4 h.Offset = binary.LittleEndian.Uint32(data[GUIDSize : GUIDSize+uint32Size]) h.Length = binary.LittleEndian.Uint32(data[GUIDSize+uint32Size : CertTableEntrySize]) return nil } // Write writes a CertTableHeaderEntry in its ABI representation to data. func (h *CertTableHeaderEntry) Write(data []byte) error { if len(data) < CertTableEntrySize { return fmt.Errorf("data too small: %v, want %v", len(data), CertTableEntrySize) } copy(data[0:GUIDSize], h.GUID[:]) uint32Size := 4 binary.LittleEndian.PutUint32(data[GUIDSize:GUIDSize+uint32Size], h.Offset) binary.LittleEndian.PutUint32(data[GUIDSize+uint32Size:CertTableEntrySize], h.Length) return nil } // ParseSnpCertTableHeader interprets the data pages from an extended guest request for certificate // information. func ParseSnpCertTableHeader(certs []byte) ([]CertTableHeaderEntry, error) { var entries []CertTableHeaderEntry var index int slice := certs[:] // Allow an empty table without the zero terminator. if len(slice) == 0 { return nil, nil } for { var next CertTableHeaderEntry if err := next.Unmarshal(slice); err != nil { return nil, fmt.Errorf("cert table index %d entry unmarshalling error: %v", index, err) } slice = slice[CertTableEntrySize:] index += CertTableEntrySize // A whole zero entry found. We're done. if next.Offset == 0 && next.Length == 0 && findNonZero(next.GUID[:], 0, 16) == GUIDSize { break } entries = append(entries, next) } // Double-check that each offset is after the header. for i, entry := range entries { if entry.Offset < uint32(index) { return nil, fmt.Errorf("cert table entry %d has invalid offset into header (size %d): %d", i, entry.Offset, index) } } return entries, nil } // Unmarshal populates the certTable with the (GUID, Blob) pairs represented in the given bytes. // The format of the bytes is specified by the SEV SNP API for extended guest requests. func (c *CertTable) Unmarshal(certs []byte) error { certTableHeader, err := ParseSnpCertTableHeader(certs) if err != nil { return err } for i, entry := range certTableHeader { var next CertTableEntry copy(next.GUID[:], entry.GUID[:]) if entry.Offset+entry.Length > uint32(len(certs)) { return fmt.Errorf("cert table entry %d specifies a byte range outside the certificate data block (size %d): offset=%d, length%d", i, len(certs), entry.Offset, entry.Length) } next.RawCert = make([]byte, entry.Length) copy(next.RawCert, certs[entry.Offset:entry.Offset+entry.Length]) c.Entries = append(c.Entries, next) } return nil } // GetByGUIDString returns the raw bytes for a certificate that matches a key identified by the // given GUID string. func (c *CertTable) GetByGUIDString(guid string) ([]byte, error) { g, err := uuid.Parse(guid) if err != nil { return nil, err } for _, entry := range c.Entries { if entry.GUID == g { return entry.RawCert, nil } } return nil, fmt.Errorf("cert not found for GUID %s", guid) } // CertsFromProto returns the CertTable represented in the given certificate chain. func CertsFromProto(chain *pb.CertificateChain) *CertTable { c := &CertTable{} if len(chain.GetArkCert()) != 0 { c.Entries = append(c.Entries, CertTableEntry{GUID: uuid.MustParse(ArkGUID), RawCert: chain.GetArkCert()}) } if len(chain.GetAskCert()) != 0 { c.Entries = append(c.Entries, CertTableEntry{GUID: uuid.MustParse(AskGUID), RawCert: chain.GetAskCert()}) } if len(chain.GetVcekCert()) != 0 { c.Entries = append(c.Entries, CertTableEntry{GUID: uuid.MustParse(VcekGUID), RawCert: chain.GetVcekCert()}) } if len(chain.GetVlekCert()) != 0 { c.Entries = append(c.Entries, CertTableEntry{GUID: uuid.MustParse(VlekGUID), RawCert: chain.GetVlekCert()}) } for guid, cert := range chain.GetExtras() { c.Entries = append(c.Entries, CertTableEntry{GUID: uuid.MustParse(guid), RawCert: cert}) } return c } // Marshal returns the CertTable in its GUID table ABI format. func (c *CertTable) Marshal() []byte { if len(c.Entries) == 0 { return nil } headerSize := uint32((len(c.Entries) + 1) * CertTableEntrySize) var dataSize uint32 for _, entry := range c.Entries { dataSize += uint32(len(entry.RawCert)) } output := make([]byte, dataSize+headerSize) cursor := headerSize for i, entry := range c.Entries { size := uint32(len(entry.RawCert)) h := &CertTableHeaderEntry{GUID: entry.GUID, Offset: cursor, Length: size} copy(output[cursor:cursor+size], entry.RawCert) h.Write(output[i*CertTableEntrySize:]) cursor += size } return output } // Proto returns the certificate chain represented in an extended guest request's // data pages. The GHCB specification allows any number of entries in the pages, // so missing certificates aren't an error. If certificates are missing, you can // choose to fetch them yourself by calling verify.GetAttestationFromReport. func (c *CertTable) Proto() *pb.CertificateChain { vcekGUID := uuid.MustParse(VcekGUID) vlekGUID := uuid.MustParse(VlekGUID) askGUID := uuid.MustParse(AskGUID) arkGUID := uuid.MustParse(ArkGUID) result := &pb.CertificateChain{Extras: make(map[string][]byte)} for _, entry := range c.Entries { switch { case entry.GUID == vcekGUID: result.VcekCert = entry.RawCert case entry.GUID == vlekGUID: result.VlekCert = entry.RawCert case entry.GUID == askGUID: result.AskCert = entry.RawCert case entry.GUID == arkGUID: result.ArkCert = entry.RawCert default: result.Extras[entry.GUID.String()] = entry.RawCert } } if len(result.VcekCert) == 0 && len(result.VlekCert) == 0 { logger.Warning("Warning: Neither VCEK nor VLEK certificate found in data pages") } return result } // cpuid returns the 4 register results of CPUID[EAX=op,ECX=0]. // See assembly implementations in cpuid_*.s var cpuid func(op uint32) (eax, ebx, ecx, edx uint32) // FmsToCpuid1Eax returns the masked CPUID_1_EAX value that represents the given // family, model, stepping (FMS) values. func FmsToCpuid1Eax(family, model, stepping byte) uint32 { var extendedFamily byte familyID := family if family >= 0xf { extendedFamily = family - 0xf familyID = 0xf } extendedModel := model >> 4 modelID := model & 0xf return (uint32(extendedFamily) << extendedFamilyShift) | (uint32(extendedModel) << extendedModelShift) | (uint32(familyID) << familyShift) | (uint32(modelID) << modelShift) | (uint32(stepping & 0xf)) } // FmsFromCpuid1Eax returns the family, model, stepping (FMS) values extracted from a // CPUID_1_EAX value. func FmsFromCpuid1Eax(eax uint32) (byte, byte, byte) { // 31:28 reserved // 27:20 Extended Family ID extendedFamily := byte((eax >> extendedFamilyShift) & 0xff) // 19:16 Extended Model ID extendedModel := byte((eax >> extendedModelShift) & 0xf) // 15:14 reserved // 11:8 Family ID familyID := byte((eax >> familyShift) & 0xf) // 7:4 Model modelID := byte((eax >> modelShift) & 0xf) // 3:0 Stepping family := extendedFamily + familyID model := (extendedModel << 4) | modelID stepping := byte(eax & 0xf) return family, model, stepping } // SevProductFromCpuid1Eax returns the SevProduct that is represented by cpuid(1).eax. func SevProductFromCpuid1Eax(eax uint32) *pb.SevProduct { family, model, stepping := FmsFromCpuid1Eax(eax) // Ah, Fh, {0h,1h} values from the KDS specification, // section "Determining the Product Name". var productName pb.SevProduct_SevProductName unknown := func() { productName = pb.SevProduct_SEV_PRODUCT_UNKNOWN stepping = 0 // Reveal nothing. } // Product information specified by processor programming reference publications. switch family { case zen3zen4Family: switch model { case milanModel: productName = pb.SevProduct_SEV_PRODUCT_MILAN case genoaModel: productName = pb.SevProduct_SEV_PRODUCT_GENOA default: unknown() } case zen5Family: switch model { case turinModel: productName = pb.SevProduct_SEV_PRODUCT_TURIN default: unknown() } default: unknown() } return &pb.SevProduct{ Name: productName, MachineStepping: &wrapperspb.UInt32Value{Value: uint32(stepping)}, } } // MaskedCpuid1EaxFromSevProduct returns the Cpuid1Eax value expected from the given product // when masked with CpuidProductMask. func MaskedCpuid1EaxFromSevProduct(product *pb.SevProduct) uint32 { if product == nil { return 0 } var family, model, stepping byte if product.MachineStepping != nil { stepping = byte(product.MachineStepping.Value & 0xf) } switch product.Name { case pb.SevProduct_SEV_PRODUCT_MILAN: family = zen3zen4Family model = milanModel case pb.SevProduct_SEV_PRODUCT_GENOA: family = zen3zen4Family model = genoaModel case pb.SevProduct_SEV_PRODUCT_TURIN: family = zen5Family model = turinModel default: return 0 } return FmsToCpuid1Eax(family, model, stepping) } // SevProduct returns the SEV product enum for the CPU that runs this // function. Ought to be called from the client, not the verifier. func SevProduct() *pb.SevProduct { // CPUID[EAX=1] is the processor info. The only bits we care about are in // the eax result. eax, _, _, _ := cpuid(1) return SevProductFromCpuid1Eax(eax & CpuidProductMask) } // MakeExtraPlatformInfo returns the representation of platform info needed on top of what an // attestation report provides in order to interpret it with the help of the AMD KDS. func MakeExtraPlatformInfo() *ExtraPlatformInfo { eax, _, _, _ := cpuid(1) return &ExtraPlatformInfo{ Size: ExtraPlatformInfoV0Size, Cpuid1Eax: eax & CpuidProductMask, } } // DefaultSevProduct returns the initial product version for a commercially available AMD SEV-SNP chip. func DefaultSevProduct() *pb.SevProduct { return &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 1}, } } // ExtraPlatformInfo represents environment information needed to interpret an attestation report when // the VCEK certificate is not available in the auxblob. type ExtraPlatformInfo struct { Size uint32 // Size doubles as Version, following the Linux ABI expansion methodology. Cpuid1Eax uint32 // Provides product information } // ParseExtraPlatformInfo extracts an ExtraPlatformInfo from a blob if it matches expectations, or // errors. func ParseExtraPlatformInfo(data []byte) (*ExtraPlatformInfo, error) { if len(data) < ExtraPlatformInfoV0Size { return nil, fmt.Errorf("%d bytes is too small for ExtraPlatformInfoSize. Want >= %d bytes", len(data), ExtraPlatformInfoV0Size) } // Populate V0 data. result := &ExtraPlatformInfo{ Size: binary.LittleEndian.Uint32(data[0:0x04]), Cpuid1Eax: binary.LittleEndian.Uint32(data[0x04:0x08]), } if uint32(len(data)) != result.Size { return nil, fmt.Errorf("actual size %d bytes != reported size %d bytes", len(data), result.Size) } return result, nil } // Marshal returns ExtraPlatformInfo in its ABI format or errors. func (i *ExtraPlatformInfo) Marshal() ([]byte, error) { if i.Size != ExtraPlatformInfoV0Size { return nil, fmt.Errorf("unsupported ExtraPlatformInfo size %d bytes", i.Size) } data := make([]byte, ExtraPlatformInfoV0Size) binary.LittleEndian.PutUint32(data[0:0x04], i.Size) binary.LittleEndian.PutUint32(data[0x04:0x08], i.Cpuid1Eax) return data, nil } // ExtendPlatformCertTable is a convenience function for parsing a CertTable, adding the // ExtraPlatformInfoGUID entry, and returning the marshaled extended table. func ExtendPlatformCertTable(data []byte, info *ExtraPlatformInfo) ([]byte, error) { certs := new(CertTable) if err := certs.Unmarshal(data); err != nil { return nil, err } // Don't extend the entries with unnecessary information about the platform // since the VCEK certificate already contains it in an extension. if _, err := certs.GetByGUIDString(VcekGUID); err == nil { return data, nil } // A directly constructed info cannot have a marshaling error. extra, err := info.Marshal() if err != nil { return nil, fmt.Errorf("could not marshal ExtraPlatformInfo: %v", err) } certs.Entries = append(certs.Entries, CertTableEntry{ GUID: uuid.MustParse(ExtraPlatformInfoGUID), RawCert: extra, }) return certs.Marshal(), nil } // ExtendedPlatformCertTable is a convenience function for parsing a CertTable, adding the // ExtraPlatformInfoGUID entry, and returning the marshaled extended table. func ExtendedPlatformCertTable(data []byte) ([]byte, error) { return ExtendPlatformCertTable(data, MakeExtraPlatformInfo()) } go-sev-guest-0.13.0/abi/abi_test.go000066400000000000000000000535371476044460400170220ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package abi import ( "bytes" "encoding/hex" "fmt" "math/rand" "runtime" "strings" "testing" "github.com/google/go-cmp/cmp" spb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/uuid" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/wrapperspb" ) var ( emptyReportV2 = ` version: 2 policy: 0xa0000 signature_algo: 1 report_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' family_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' image_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' measurement: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' host_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' id_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' author_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id_ma: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' chip_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` emptyReportV3 = ` version: 3 policy: 0xa0000 signature_algo: 1 report_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' family_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' image_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' measurement: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' host_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' id_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' author_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id_ma: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' cpuid1eax_fms: 0 chip_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` ) func TestMbz64(t *testing.T) { tests := []struct { data uint64 lo int hi int wantErr string }{ { data: uint64(0), lo: 0, hi: 63, }, { data: ^uint64(0) &^ (uint64(1<<31) | uint64(1<<32) | uint64(1<<33)), lo: 31, hi: 33, }, { data: ^uint64(0) &^ (uint64(1<<0x1f) | uint64(1<<0x20)), lo: 0x1f, hi: 0x21, wantErr: "mbz range test[0x1f:0x21] not all zero", }, { data: ^uint64(0) &^ (uint64(1<<0x20) | uint64(1<<0x21)), lo: 0x1f, hi: 0x21, wantErr: "mbz range test[0x1f:0x21] not all zero", }, } for _, tc := range tests { err := mbz64(tc.data, "test", tc.hi, tc.lo) if (tc.wantErr == "" && err != nil) || (tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr))) { t.Errorf("mbz64(0x%x, %d, %d) = %v, want %q", tc.data, tc.hi, tc.lo, err, tc.wantErr) } } } func TestReportMbz(t *testing.T) { tests := []struct { name string report string changeIndex int changeValue byte wantErr string }{ { name: "AuthorKeyEn reserved", changeIndex: 0x49, wantErr: "mbz range data[0x48:0x4C][0x5:0x1f] not all zero: cc00", }, { name: "pre-report data", changeIndex: 0x4f, wantErr: "mbz range [0x4c:0x50] not all zero: 000000cc", }, { name: "pre-chip id", changeIndex: 0x18A, wantErr: "mbz range [0x188:0x1a0] not all zero: 0000cc", }, { name: "current reserved", changeIndex: 0x1EB, wantErr: "mbz range [0x1eb:0x1ec] not all zero: cc", }, { name: "committed reserved", changeIndex: 0x1EF, wantErr: "mbz range [0x1ef:0x1f0] not all zero: cc", }, { name: "pre-signature reserved", changeIndex: 0x1f9, wantErr: "mbz range [0x1f8:0x2a0] not all zero: 00cc", }, { name: "post-ecdsa signature reserved", changeIndex: signatureOffset + EcdsaP384Sha384SignatureSize + 2, wantErr: "mbz range [0x330:0x4a0] not all zero: 0000cc", }, { name: "Guest policy bit 17", changeIndex: policyOffset + 2, // Bits 16-23 changeValue: 0x1d, // Set bits 16, 18, 19, 20 wantErr: "policy[17] is reserved, must be 1, got 0", }, { name: "Guest policy bit 21", changeIndex: policyOffset + 2, // Bits 16-23 changeValue: 0x22, // Set bits 17, 21 wantErr: "malformed guest policy: mbz range policy[0x15:0x3f] not all zero: 220000", }, } reportProto := &spb.Report{} if err := prototext.Unmarshal([]byte(emptyReportV2), reportProto); err != nil { t.Fatalf("test failure: %v", err) } for _, tc := range tests { // Everything but the signature hase raw, err := ReportToAbiBytes(reportProto) if err != nil { t.Fatalf("%s: test failure: ReportToAbiBytes(%v) errored unexpectedly: %v", tc.name, reportProto, err) } changeValue := byte(0xcc) if tc.changeValue != 0 { changeValue = tc.changeValue } raw[tc.changeIndex] = changeValue if _, err := ReportToProto(raw); !strings.Contains(err.Error(), tc.wantErr) { t.Errorf("%s: ReportToProto(%v) = _, %v. Want error %v", tc.name, reportProto, err, tc.wantErr) } } reportProto = &spb.Report{} if err := prototext.Unmarshal([]byte(emptyReportV3), reportProto); err != nil { t.Fatalf("test failure: %v", err) } raw, err := ReportToAbiBytes(reportProto) if err != nil { t.Fatalf("%s: test failure: ReportToAbiBytes(reportV3) errored unexpectedly: %v", reportProto, err) } raw[0x188] = 0xcc raw[0x189] = 0xcc raw[0x18A] = 0xcc if _, err := ReportToProto(raw); err != nil { t.Errorf("ReportToProto(%v) = _, %v. Want nil", reportProto, err) } } func TestSnpPolicySection(t *testing.T) { entropySize := 128 entropy := make([]uint8, entropySize) rand.Read(entropy) for tc := 0; tc < entropySize/3; tc++ { policy := SnpPolicy{ ABIMinor: entropy[tc*3], ABIMajor: entropy[tc*3+1], SMT: (entropy[tc*3+2] & 1) != 0, MigrateMA: (entropy[tc*3+2] & 2) != 0, Debug: (entropy[tc*3+2] & 4) != 0, SingleSocket: (entropy[tc*3+2] & 8) != 0, } got, err := ParseSnpPolicy(SnpPolicyToBytes(policy)) if err != nil { t.Errorf("ParseSnpPolicy(SnpPolicyToBytes(%v)) errored unexpectedly: %v", policy, err) } if got != policy { t.Errorf("ParseSnpPolicy(SnpPolicyToBytes(%v)) = %v, want %v", policy, got, policy) } } } func TestSnpPlatformInfo(t *testing.T) { tests := []struct { input uint64 want SnpPlatformInfo wantErr string }{ { input: 0, }, { input: 3, want: SnpPlatformInfo{TSMEEnabled: true, SMTEnabled: true}, }, { input: 21, want: SnpPlatformInfo{ SMTEnabled: true, ECCEnabled: true, CiphertextHidingDRAMEnabled: true, }, }, { input: 42, want: SnpPlatformInfo{ TSMEEnabled: true, RAPLDisabled: true, AliasCheckComplete: true, }, }, { input: 63, want: SnpPlatformInfo{ TSMEEnabled: true, SMTEnabled: true, ECCEnabled: true, RAPLDisabled: true, CiphertextHidingDRAMEnabled: true, AliasCheckComplete: true, }, }, { input: 64, wantErr: "unrecognized platform info bit(s): 0x40", }, } for _, tc := range tests { got, err := ParseSnpPlatformInfo(tc.input) if (err != nil && (tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr))) || (err == nil && tc.wantErr != "") { t.Errorf("ParseSnpPlatformInfo(%x) errored unexpectedly. Got %v, want %v", tc.input, err, tc.wantErr) } if err == nil && tc.want != got { t.Errorf("ParseSnpPlatformInfo(%x) = %v, want %v", tc.input, got, tc.want) } } } func TestCpuid(t *testing.T) { // GitHub actions may run on AARCH64 if runtime.GOARCH != "amd64" { t.Skip() } a, b, c, d := cpuid(1) if (a | b | c | d) == 0 { t.Errorf("cpuid(1) = 0, 0, 0, 0") } } type testCertTable struct { table []byte extraraw []byte } const extraGUID = "00000000-0000-c0de-0000-000000000000" func testRawCertTable(t testing.TB) *testCertTable { t.Helper() headers := make([]CertTableHeaderEntry, 6) // ARK, ASK, VCEK, VLEK, extra, NULL arkraw := []byte("ark") askraw := []byte("ask") vcekraw := []byte("vcek") vlekraw := []byte("vlek") extraraw := []byte("extra") headers[0].GUID = uuid.MustParse(ArkGUID) headers[0].Offset = uint32(len(headers) * CertTableEntrySize) headers[0].Length = uint32(len(arkraw)) headers[1].GUID = uuid.MustParse(AskGUID) headers[1].Offset = headers[0].Offset + headers[0].Length headers[1].Length = uint32(len(askraw)) headers[2].GUID = uuid.MustParse(VcekGUID) headers[2].Offset = headers[1].Offset + headers[1].Length headers[2].Length = uint32(len(vcekraw)) headers[3].GUID = uuid.MustParse(VlekGUID) headers[3].Offset = headers[2].Offset + headers[2].Length headers[3].Length = uint32(len(vlekraw)) headers[4].GUID = uuid.MustParse(extraGUID) headers[4].Offset = headers[3].Offset + headers[3].Length headers[4].Length = uint32(len(extraraw)) result := &testCertTable{ table: make([]byte, headers[4].Offset+headers[4].Length), extraraw: extraraw, } for i, cert := range [][]byte{arkraw, askraw, vcekraw, vlekraw, extraraw} { if err := (&headers[i]).Write(result.table[i*CertTableEntrySize:]); err != nil { t.Fatalf("could not write header %d: %v", i, err) } copy(result.table[headers[i].Offset:], cert) } return result } func testRawCertTableNoVcek(t testing.TB) *testCertTable { t.Helper() headers := make([]CertTableHeaderEntry, 2) // extra, NULL extraraw := []byte("extra") headers[0].GUID = uuid.MustParse(extraGUID) headers[0].Offset = uint32(len(headers) * CertTableEntrySize) headers[0].Length = uint32(len(extraraw)) result := &testCertTable{ table: make([]byte, headers[0].Offset+headers[0].Length), extraraw: extraraw, } for i, cert := range [][]byte{extraraw} { if err := (&headers[i]).Write(result.table[i*CertTableEntrySize:]); err != nil { t.Fatalf("could not write header %d: %v", i, err) } copy(result.table[headers[i].Offset:], cert) } return result } func TestCertTableProto(t *testing.T) { result := testRawCertTable(t) c := new(CertTable) if err := c.Unmarshal(result.table); err != nil { t.Errorf("c.Unmarshal(%s) = %v, want nil", hex.Dump(result.table), err) } p := c.Proto() if len(p.Extras) != 1 { t.Fatalf("got cert table Extras length %d, want 1", len(p.Extras)) } gotExtra, ok := p.Extras[extraGUID] if !ok || !bytes.Equal(gotExtra, result.extraraw) { t.Fatalf("Extras[%q] = %v, want %v", extraGUID, gotExtra, result.extraraw) } bs := c.Marshal() if !bytes.Equal(bs, result.table) { t.Errorf("c.Marshal() = %v, want %v", bs, result.table) } } func TestSevProduct(t *testing.T) { oldCpuid := cpuid defer func() { cpuid = oldCpuid }() tcs := []struct { eax uint32 want *spb.SevProduct }{ { eax: 0x00a00f10, want: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 0}, }, }, { eax: 0x00a00f11, want: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 1}, }, }, { eax: 0x00a10f10, want: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: &wrapperspb.UInt32Value{Value: 0}, }, }, { eax: 0x00a10f12, want: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: &wrapperspb.UInt32Value{Value: 2}, }, }, { eax: 0x0b010f0, want: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_UNKNOWN, MachineStepping: &wrapperspb.UInt32Value{Value: 0}, }, }, { eax: 0x00b00f21, want: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_TURIN, MachineStepping: &wrapperspb.UInt32Value{Value: 1}, }, }, } for _, tc := range tcs { t.Run(fmt.Sprintf("EAX_0x%x", tc.eax), func(t *testing.T) { cpuid = func(uint32) (uint32, uint32, uint32, uint32) { return tc.eax, 0, 0, 0 } got := SevProduct() if diff := cmp.Diff(got, tc.want, protocmp.Transform()); diff != "" { t.Errorf("SevProduct() = %+v, want %+v. Diff: %s", got, tc.want, diff) } got2 := SevProductFromCpuid1Eax(tc.eax) if diff := cmp.Diff(got2, got, protocmp.Transform()); diff != "" { t.Errorf("SevProductFromCpuid1Eax(0x%x) = %+v, want %+v. Diff: %s", tc.eax, got2, tc.want, diff) } }) } } func TestExtendedPlatformCertTableConservation(t *testing.T) { // If VCEK is in the cert table, then the product info isn't added to the cert table. table := testRawCertTable(t).table oldt := new(CertTable) _ = oldt.Unmarshal(table) pold := oldt.Proto() nextTable, err := ExtendedPlatformCertTable(table) if err != nil { t.Fatalf("ExtendedPlatformCertTable(%v) =_, %v. Want nil", table, err) } newt := new(CertTable) if err := newt.Unmarshal(nextTable); err != nil { t.Fatalf("ExtendedPlatformCertTable(_) _ %v, which could not be unmarshaled: %v", nextTable, err) } pnew := newt.Proto() if len(pnew.Extras) != len(pold.Extras) { t.Fatalf("ExtendedPlatformCertTable(_) table extras size is %d, want %d", len(pnew.Extras), len(pold.Extras)) } } func TestExtendedPlatformCertTable(t *testing.T) { oldCpuid := cpuid defer func() { cpuid = oldCpuid }() table := testRawCertTableNoVcek(t).table oldt := new(CertTable) _ = oldt.Unmarshal(table) pold := oldt.Proto() tcs := []struct { name string pname spb.SevProduct_SevProductName eax uint32 stepping uint32 }{ {name: "Genoa-B2", pname: spb.SevProduct_SEV_PRODUCT_GENOA, eax: 0x00a10f12, stepping: 2}, {name: "Milan-B1", pname: spb.SevProduct_SEV_PRODUCT_MILAN, eax: 0x00a00f11, stepping: 1}, {name: "Milan-B0", pname: spb.SevProduct_SEV_PRODUCT_MILAN, eax: 0x00a00f10, stepping: 0}, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { cpuid = func(uint32) (uint32, uint32, uint32, uint32) { return tc.eax, 0, 0, 0 } nextTable, err := ExtendedPlatformCertTable(table) if err != nil { t.Fatalf("ExtendedPlatformCertTable(%v) =_, %v. Want nil", table, err) } newt := new(CertTable) if err := newt.Unmarshal(nextTable); err != nil { t.Fatalf("ExtendedPlatformCertTable(_) _ %v, which could not be unmarshaled: %v", nextTable, err) } pnew := newt.Proto() if len(pnew.Extras) != len(pold.Extras)+1 { t.Fatalf("ExtendedPlatformCertTable(_) table extras size is %d, want %d", len(pnew.Extras), len(pold.Extras)+1) } blob, ok := pnew.Extras[ExtraPlatformInfoGUID] if !ok { t.Fatalf("ExtendedPlatfromCertTable(_) table %v extras missing ExtraPlatformInfoGUID", pnew) } info, err := ParseExtraPlatformInfo(blob) if err != nil { t.Fatalf("ParseExtraPlatformInfo(%v) = _, %v. Want nil", blob, err) } if info.Size != ExtraPlatformInfoV0Size { t.Errorf("ExtraPltaformInfo Size %d is not %d", info.Size, ExtraPlatformInfoV0Size) } if info.Cpuid1Eax != tc.eax&CpuidProductMask { t.Errorf("ExtraPlatformInfo Cpuid1Eax 0x%x is not 0x%x", info.Cpuid1Eax, tc.eax&CpuidProductMask) } got := SevProductFromCpuid1Eax(info.Cpuid1Eax) want := &spb.SevProduct{Name: tc.pname, MachineStepping: &wrapperspb.UInt32Value{Value: tc.stepping}} if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" { t.Errorf("ExtraPlatformInfo Cpuid1Eax product %v is not %v: %s", got, want, diff) } }) } } go-sev-guest-0.13.0/abi/amdsp.go000066400000000000000000000144641476044460400163300ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package abi import "fmt" // SevFirmwareStatus is the type of all AMD-SP firmware status codes, as documented in the SEV API // https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf type SevFirmwareStatus int64 // Unexported errors are not expected to leave the kernel. const ( // Success denotes successful completion of a firmware command. Success SevFirmwareStatus = 0 // InvalidPlatformState is the code for the platform to be in the wrong state for a given command. InvalidPlatformState = 1 // InvalidGuestState is the code for the guest to be in the wrong state for a given command. InvalidGuestState = 2 // Platform owner error unexpected by guest command. // invalidConfig = 3 // InvalidLength is the code for a provided buffer size is too small to complete the command. InvalidLength = 4 // Platform owner error unexpected by guest command. // alreadyOwned = 5 // Platform owner error unexpected by guest command. // invalidCertificate = 6 // PolicyFailure is the code for when the guest policy disallows the command. PolicyFailure = 7 // Inactive is the code for when a command is sent for a guest, but the guest is inactive. Inactive = 8 // InvalidAddress is the code for when a provided address is invalid. InvalidAddress = 9 // User error expected at launch, unexpected here. // badSignature = 10 // User error expected at launch, unexpected here. // badMeasurement = 11 // Kernel error, unexpected. // asidOwned = 12 // Kernel error, unexpected. // invalidAsid = 13 // Kernel error, unexpected. // wbinvdRequired = 14 // Kernel error, unexpected. // dfFlushRequired = 15 // Kernel error, unexpected. // invalidGuest = 16 // InvalidCommand is the code for when the command code is invalid. InvalidCommand = 17 // Kernel error, unexpected. // active = 18 // HwErrorPlatform is the code for when the hardware failed but it's okay to update its buffers. HwErrorPlatform = 19 // HwErrorUnsafe is the code for when the hardware failed and it's unsafe to update its buffers. HwErrorUnsafe = 20 // Unsupported is for an unsupported feature. Unsupported = 21 // InvalidParam is the code for an invalid parameter in a command. InvalidParam = 22 // ResourceLimit is the code for when the firmware has reached a resource limit and can't complete the command. ResourceLimit = 23 // SecureDataInvalid is the code for when a hardware integrity check has failed. SecureDataInvalid = 24 // InvalidPageSize indicates an RMP error with the recorded page size. InvalidPageSize = 25 // InvalidPageState indicates an RMP error with the recorded page state. InvalidPageState = 26 // InvalidMdataEntry indicates an RMP error with the recorded metadata. InvalidMdataEntry = 27 // InvalidPageOwner indicates an RMP error with ASID mismatch between accessors. InvalidPageOwner = 28 // AeadOflow indicates that firmware memory capacity is reached in the AEAD cryptographic algorithm. AeadOflow = 29 // Skip code 0x1E since AeaedOflow is 0x1D and rbModeExited is 0x1F. // reserved1e = 30 // Kernel error, unexpected. // rbModeExited = 31 // Kernel error, unexpected. // rmpInitRequired = 32 // Platform management error, unexpected. // badSvn = 33 // Platform management error, unexpected. // badVersion = 34 // Platform management error, unexpected. // shutdownRequired = 35 // Platform management error, unexpected. // updateFailed = 36 // Platform management error, unexpected. // restoreRequired = 37 ) // GuestRequestInvalidLength is set by the ccp driver and not the AMD-SP when an guest extended // request provides too few pages for the firmware to populate with data. const GuestRequestInvalidLength SevFirmwareStatus = 0x100000000 // SevFirmwareErr is an error that interprets firmware status codes from the AMD secure processor. type SevFirmwareErr struct { Status SevFirmwareStatus } func (e *SevFirmwareErr) Error() string { if e.Status == Success { return "success" } if e.Status == InvalidPlatformState { return "platform state is invalid for this command" } if e.Status == InvalidGuestState { return "guest state is invalid for this command" } if e.Status == InvalidLength { return "memory buffer is too small (library bug, please report)" } if e.Status == PolicyFailure { return "request is not allowed by guest policy" } if e.Status == Inactive { return "guest is inactive" } if e.Status == InvalidAddress { return "address provided is invalid (library bug, please report)" } if e.Status == InvalidCommand { return "invalid command (library bug, please report)" } if e.Status == HwErrorPlatform { return "hardware condition has occurred affecting the platform (report to sysadmin)" } if e.Status == HwErrorUnsafe { return "hardware condition has occurred affecting the platform. Buffers unsafe (report to sysadmin)" } if e.Status == Unsupported { return "unsupported feature" } if e.Status == InvalidParam { return "invalid parameter (library bug, please report)" } if e.Status == ResourceLimit { return "SEV firmware has run out of recources necessary to complete the command" } if e.Status == SecureDataInvalid { return "part-specific SEV data failed integrity checks (report to sysadmin)" } if e.Status == InvalidPageSize { return "RMP: invalid page size" } if e.Status == InvalidPageState { return "RMP: invalid page state" } if e.Status == InvalidMdataEntry { return "RMP: invalid recorded metadata" } if e.Status == InvalidPageOwner { return "RMP: ASID mismatch between accessors" } if e.Status == AeadOflow { return "AMD-SP firmware memory would be over capacity for AEAD use" } if e.Status == GuestRequestInvalidLength { return "too few extended guest request data pages" } return fmt.Sprintf("unexpected firmware status (see SEV API spec): %x", uint64(e.Status)) } go-sev-guest-0.13.0/abi/cpuid.go000066400000000000000000000002401476044460400163130ustar00rootroot00000000000000//go:build !amd64 || gccgo // +build !amd64 gccgo package abi func init() { cpuid = func(uint32) (uint32, uint32, uint32, uint32) { return 0, 0, 0, 0 } } go-sev-guest-0.13.0/abi/cpuid_amd64.go000066400000000000000000000002301476044460400173050ustar00rootroot00000000000000//go:build amd64 && !gccgo // +build amd64,!gccgo package abi func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) func init() { cpuid = asmCpuid } go-sev-guest-0.13.0/abi/cpuid_amd64.s000066400000000000000000000014661476044460400171560ustar00rootroot00000000000000// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //+build amd64,!gccgo // func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) TEXT ·asmCpuid(SB), 7, $0 XORQ CX, CX MOVL op+0(FP), AX CPUID MOVL AX, eax+8(FP) MOVL BX, ebx+12(FP) MOVL CX, ecx+16(FP) MOVL DX, edx+20(FP) RET go-sev-guest-0.13.0/client/000077500000000000000000000000001476044460400154075ustar00rootroot00000000000000go-sev-guest-0.13.0/client/client.go000066400000000000000000000262551476044460400172260ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "errors" "flag" "fmt" "github.com/google/go-sev-guest/abi" labi "github.com/google/go-sev-guest/client/linuxabi" pb "github.com/google/go-sev-guest/proto/sevsnp" ) var sevGuestPath = flag.String("sev_guest_device_path", "default", "Path to SEV guest device. If \"default\", uses platform default or a fake if testing.") // Device encapsulates the possible commands to the AMD SEV guest device. type Device interface { // Open prepares the Device from the given path. Open(path string) error // Close releases the device resource. Close() error // Ioctl performs the given command with the given argument. Ioctl(command uintptr, argument any) (uintptr, error) // Product returns AMD SEV-related CPU information of the calling CPU. Product() *pb.SevProduct } // LeveledQuoteProvider encapsulates calls to collect an extended attestation report at a given // privilege level. type LeveledQuoteProvider interface { // IsSupported returns whether the kernel supports this implementation. IsSupported() bool // GetRawQuote returns a raw report with the given privilege level. GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error) // Product returns AMD SEV-related CPU information of the calling CPU. // // Deprecated: Use abi.ExtraPlatformInfoGUID in raw quote certificate table. Product() *pb.SevProduct } // QuoteProvider encapsulates calls to collect an extended attestation report. type QuoteProvider interface { // IsSupported returns whether the kernel supports this implementation. IsSupported() bool // GetRawQuote returns a raw report with the default privilege level. GetRawQuote(reportData [64]byte) ([]uint8, error) // Product returns AMD SEV-related CPU information of the calling CPU. // // Deprecated: Use abi.ExtraPlatformInfoGUID in the raw quote certificate table. Product() *pb.SevProduct } // UseDefaultSevGuest returns true iff -sev_guest_device_path=default. func UseDefaultSevGuest() bool { return *sevGuestPath == "default" } func message(d Device, command uintptr, req *labi.SnpUserGuestRequest) error { result, err := d.Ioctl(command, req) if err != nil { // The ioctl could have failed with a firmware error that // indicates a problem certificate length. We need to // communicate that specifically. if req.FwErr != 0 { return &abi.SevFirmwareErr{Status: abi.SevFirmwareStatus(req.FwErr)} } return err } if result != uintptr(labi.EsOk) { return &labi.SevEsErr{Result: labi.EsResult(result)} } return nil } // GetRawReportAtVmpl requests for an attestation report at the given VMPL that incorporates the // given user data. // // Deprecated: Use LeveledQuoteProvider. func GetRawReportAtVmpl(d Device, reportData [64]byte, vmpl int) ([]byte, error) { var snpReportRsp labi.SnpReportRespABI userGuestReq := labi.SnpUserGuestRequest{ ReqData: &labi.SnpReportReqABI{ ReportData: reportData, Vmpl: uint32(vmpl), }, RespData: &snpReportRsp, } if err := message(d, labi.IocSnpGetReport, &userGuestReq); err != nil { return nil, err } return snpReportRsp.Data[:abi.ReportSize], nil } // GetRawReport requests for an attestation report at VMPL0 that incorporates the given user data. // // Deprecated: Use QuoteProvider. func GetRawReport(d Device, reportData [64]byte) ([]byte, error) { return GetRawReportAtVmpl(d, reportData, 0) } // GetReportAtVmpl gets an attestation report at the given VMPL into its protobuf representation. // // Deprecated: Use GetQuoteProtoAtLevel. func GetReportAtVmpl(d Device, reportData [64]byte, vmpl int) (*pb.Report, error) { data, err := GetRawReportAtVmpl(d, reportData, vmpl) if err != nil { return nil, err } return abi.ReportToProto(data) } // GetReport gets an attestation report at VMPL0 into its protobuf representation. // // Deprecated: Use GetQuoteProto. func GetReport(d Device, reportData [64]byte) (*pb.Report, error) { return GetReportAtVmpl(d, reportData, 0) } // getExtendedReportIn issues a GetExtendedReport command to the sev-guest driver with reportData // input and certs as a destination for certificate data. If certs is empty, this function returns // the expected size of certs as its second result value. If certs is non-empty, this function // returns the signed attestation report containing reportData and the certificate chain for the // report's endorsement key. func getExtendedReportIn(d Device, reportData [64]byte, vmpl int, certs []byte) ([]byte, uint32, error) { var snpReportRsp labi.SnpReportRespABI snpExtReportReq := labi.SnpExtendedReportReq{ Data: labi.SnpReportReqABI{ ReportData: reportData, Vmpl: uint32(vmpl), }, Certs: certs, CertsLength: uint32(len(certs)), } userGuestReq := labi.SnpUserGuestRequest{ ReqData: &snpExtReportReq, RespData: &snpReportRsp, } // Query the length required for certs. if err := message(d, labi.IocSnpGetExtendedReport, &userGuestReq); err != nil { var fwErr *abi.SevFirmwareErr if errors.As(err, &fwErr) && fwErr.Status == abi.GuestRequestInvalidLength { return nil, snpExtReportReq.CertsLength, nil } return nil, 0, err } return snpReportRsp.Data[:abi.ReportSize], snpExtReportReq.CertsLength, nil } // queryCertificateLength requests the required memory size in bytes to represent all certificates // returned by an extended guest request. func queryCertificateLength(d Device, vmpl int) (uint32, error) { _, length, err := getExtendedReportIn(d, [64]byte{}, vmpl, []byte{}) if err != nil { return 0, err } return length, nil } // GetRawExtendedReportAtVmpl requests for an attestation report that incorporates the given user // data at the given VMPL, and additional key certificate information. // // Deprecated: Use LeveledQuoteProvider. func GetRawExtendedReportAtVmpl(d Device, reportData [64]byte, vmpl int) ([]byte, []byte, error) { length, err := queryCertificateLength(d, vmpl) if err != nil { return nil, nil, fmt.Errorf("error querying certificate length: %v", err) } certs := make([]byte, length) report, _, err := getExtendedReportIn(d, reportData, vmpl, certs) if err != nil { return nil, nil, err } return report, certs, nil } // GetRawExtendedReport requests for an attestation report that incorporates the given user data, // and additional key certificate information. // // Deprecated: Use QuoteProvider. func GetRawExtendedReport(d Device, reportData [64]byte) ([]byte, []byte, error) { return GetRawExtendedReportAtVmpl(d, reportData, 0) } // GetQuoteProto uses the given QuoteProvider to return the // protobuf representation of an attestation report with cached // certificate chain. func GetQuoteProto(qp QuoteProvider, reportData [64]byte) (*pb.Attestation, error) { reportcerts, err := qp.GetRawQuote(reportData) if err != nil { return nil, err } attestation, err := abi.ReportCertsToProto(reportcerts) if err != nil { return nil, err } // TODO(Issue#109): Remove when Product is removed. attestation.Product = qp.Product() return attestation, nil } // GetQuoteProtoAtLevel uses the given LeveledQuoteProvider to return the // protobuf representation of an attestation report at a given VMPL with cached // certificate chain. func GetQuoteProtoAtLevel(qp LeveledQuoteProvider, reportData [64]byte, vmpl uint) (*pb.Attestation, error) { reportcerts, err := qp.GetRawQuoteAtLevel(reportData, vmpl) if err != nil { return nil, err } attestation, err := abi.ReportCertsToProto(reportcerts) if err != nil { return nil, err } attestation.Product = qp.Product() return attestation, nil } // GetExtendedReportAtVmpl gets an extended attestation report at the given VMPL into a structured type. // // Deprecated: Use GetQuoteProtoAtLevel func GetExtendedReportAtVmpl(d Device, reportData [64]byte, vmpl int) (*pb.Attestation, error) { reportBytes, certBytes, err := GetRawExtendedReportAtVmpl(d, reportData, vmpl) if err != nil { return nil, err } report, err := abi.ReportToProto(reportBytes) if err != nil { return nil, err } certs := new(abi.CertTable) if err := certs.Unmarshal(certBytes); err != nil { return nil, err } return &pb.Attestation{ Report: report, CertificateChain: certs.Proto(), Product: d.Product(), }, nil } // GetExtendedReport gets an extended attestation report at VMPL0 into a structured type. // // Deprecated: Use GetQuoteProto. func GetExtendedReport(d Device, reportData [64]byte) (*pb.Attestation, error) { return GetExtendedReportAtVmpl(d, reportData, 0) } // GuestFieldSelect represents which guest-provided information will be mixed into a derived key. type GuestFieldSelect struct { TCBVersion bool GuestSVN bool Measurement bool FamilyID bool ImageID bool GuestPolicy bool } // SnpDerivedKeyReq represents a request to the SEV guest device to derive a key from specified // information. type SnpDerivedKeyReq struct { // UseVCEK determines if the derived key will be based on VCEK or VMRK. This is opposite from the // ABI's ROOT_KEY_SELECT to avoid accidentally making an unsafe choice in a multitenant // environment. UseVCEK bool GuestFieldSelect GuestFieldSelect // Vmpl to mix into the key. Must be greater than or equal to current Vmpl. Vmpl uint32 // GuestSVN to mix into the key. Must be less than or equal to GuestSVN at launch. GuestSVN uint32 // TCBVersion to mix into the key. Must be less than or equal to the CommittedTcb. TCBVersion uint64 } // ABI returns the SNP ABI-specified uint64 bitmask of guest field selection. func (g GuestFieldSelect) ABI() uint64 { var value uint64 if g.TCBVersion { value |= uint64(1 << 5) } if g.GuestSVN { value |= uint64(1 << 4) } if g.Measurement { value |= uint64(1 << 3) } if g.FamilyID { value |= uint64(1 << 2) } if g.ImageID { value |= uint64(1 << 1) } if g.GuestPolicy { value |= uint64(1 << 0) } return value } // GetDerivedKeyAcknowledgingItsLimitations returns 32 bytes of key material that the AMD security // processor derives from the given parameters. Security limitations of this command are described // more in the project README. func GetDerivedKeyAcknowledgingItsLimitations(d Device, request *SnpDerivedKeyReq) (*labi.SnpDerivedKeyRespABI, error) { response := &labi.SnpDerivedKeyRespABI{} rootKeySelect := uint32(1) if request.UseVCEK { rootKeySelect = 0 } guestRequest := &labi.SnpUserGuestRequest{ ReqData: &labi.SnpDerivedKeyReqABI{ RootKeySelect: rootKeySelect, GuestFieldSelect: request.GuestFieldSelect.ABI(), Vmpl: request.Vmpl, GuestSVN: request.GuestSVN, TCBVersion: request.TCBVersion, }, RespData: response, } if err := message(d, labi.IocSnpGetDerivedKey, guestRequest); err != nil { return nil, fmt.Errorf("error getting derived key: %v", err) } return response, nil } go-sev-guest-0.13.0/client/client_linux.go000066400000000000000000000215031476044460400204340ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build linux || freebsd || openbsd || netbsd // Package client provides an interface to the AMD SEV-SNP guest device commands. package client import ( "flag" "fmt" "strconv" "time" "github.com/google/go-configfs-tsm/configfs/linuxtsm" "github.com/google/go-configfs-tsm/report" "github.com/google/go-sev-guest/abi" labi "github.com/google/go-sev-guest/client/linuxabi" spb "github.com/google/go-sev-guest/proto/sevsnp" "golang.org/x/sys/unix" ) const ( // defaultSevGuestDevicePath is the platform's usual device path to the SEV guest. defaultSevGuestDevicePath = "/dev/sev-guest" installURL = "https://github.com/google/go-sev-guest/blob/main/INSTALL.md" ) // These flags should not be needed for long term health of the project as the Linux kernel // catches up with throttling-awareness. var ( throttleDuration = flag.Duration("self_throttle_duration", 2*time.Second, "Rate-limit library-initiated device commands to this duration") burstMax = flag.Int("self_throttle_burst", 1, "Rate-limit library-initiated device commands to this many commands per duration") defaultVMPL = flag.String("default_vmpl", "", "Default VMPL to use for attestation (empty for driver default)") ) // LinuxDevice implements the Device interface with Linux ioctls. type LinuxDevice struct { fd int lastCmd time.Time burst int } // Open opens the SEV-SNP guest device from a given path func (d *LinuxDevice) Open(path string) error { fd, err := unix.Open(path, unix.O_RDWR, 0) if err != nil { d.fd = -1 return fmt.Errorf("could not open AMD SEV guest device at %s (see %s): %v", path, installURL, err) } d.fd = fd return nil } // OpenDevice opens the SEV-SNP guest device. func OpenDevice() (*LinuxDevice, error) { result := &LinuxDevice{} path := *sevGuestPath if UseDefaultSevGuest() { path = defaultSevGuestDevicePath } if err := result.Open(path); err != nil { return nil, err } return result, nil } // Close closes the SEV-SNP guest device. func (d *LinuxDevice) Close() error { if d.fd == -1 { // Not open return nil } if err := unix.Close(d.fd); err != nil { return err } // Prevent double-close. d.fd = -1 return nil } // Ioctl sends a command with its wrapped request and response values to the Linux device. func (d *LinuxDevice) Ioctl(command uintptr, req any) (uintptr, error) { // TODO(Issue #40): Remove the workaround to the ENOTTY lockout when throttled // in Linux 6.1 by throttling ourselves first. if d.burst == 0 { sinceLast := time.Since(d.lastCmd) // Self-throttle for tests without guest OS throttle detection if sinceLast < *throttleDuration { time.Sleep(*throttleDuration - sinceLast) } } switch sreq := req.(type) { case *labi.SnpUserGuestRequest: abi := sreq.ABI() result, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(d.fd), command, uintptr(abi.Pointer())) abi.Finish(sreq) d.burst = (d.burst + 1) % *burstMax if d.burst == 0 { d.lastCmd = time.Now() } // TODO(Issue #5): remove the work around for the kernel bug that writes // uninitialized memory back on non-EIO. if errno != unix.EIO { sreq.FwErr = 0 } if errno != 0 { return 0, errno } return result, nil } return 0, fmt.Errorf("unexpected request value: %v", req) } // Product returns the current CPU's associated AMD SEV product information. func (d *LinuxDevice) Product() *spb.SevProduct { return abi.SevProduct() } // LinuxIoctlQuoteProvider implements the QuoteProvider interface to fetch // attestation quote via the deprecated /dev/sev-guest ioctl. type LinuxIoctlQuoteProvider struct{} // IsSupported checks if TSM client can be created to use /dev/sev-guest ioctl. func (p *LinuxIoctlQuoteProvider) IsSupported() bool { d, err := OpenDevice() if err != nil { return false } d.Close() return true } // GetRawQuoteAtLevel returns byte format attestation plus certificate table via /dev/sev-guest ioctl. func (p *LinuxIoctlQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]uint8, error) { d, err := OpenDevice() if err != nil { return nil, err } defer d.Close() // If there are no certificates, then just return the raw report. length, err := queryCertificateLength(d, int(level)) if err != nil { return GetRawReportAtVmpl(d, reportData, int(level)) } certs := make([]byte, length) report, _, err := getExtendedReportIn(d, reportData, int(level), certs) if err != nil { return nil, err } // Mix the platform info in with the auxblob. extended, err := abi.ExtendedPlatformCertTable(certs) if err != nil { return nil, fmt.Errorf("invalid certificate table: %v", err) } return append(report, extended...), nil } // GetRawQuote returns byte format attestation plus certificate table via /dev/sev-guest ioctl. func (p *LinuxIoctlQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { if *defaultVMPL == "" { return p.GetRawQuoteAtLevel(reportData, 0) } vmpl, err := strconv.ParseUint(*defaultVMPL, 10, 32) if err != nil { return nil, fmt.Errorf("bad default_vmpl: %q", *defaultVMPL) } return p.GetRawQuoteAtLevel(reportData, uint(vmpl)) } // Product returns the current CPU's associated AMD SEV product information. // // Deprecated: Use ExtraPlatformInfoGUID from the cert table. func (*LinuxIoctlQuoteProvider) Product() *spb.SevProduct { return abi.SevProduct() } // LinuxConfigFsQuoteProvider implements the QuoteProvider interface to fetch // attestation quote via ConfigFS. type LinuxConfigFsQuoteProvider struct{} // IsSupported checks if TSM client can be created to use ConfigFS system. func (p *LinuxConfigFsQuoteProvider) IsSupported() bool { c, err := linuxtsm.MakeClient() if err != nil { return false } r, err := report.Create(c, &report.Request{}) if err != nil { return false } provider, err := r.ReadOption("provider") return err == nil && string(provider) == "sev_guest\n" } // GetRawQuoteAtLevel returns byte format attestation plus certificate table via ConfigFS. func (p *LinuxConfigFsQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]uint8, error) { req := &report.Request{ InBlob: reportData[:], GetAuxBlob: true, Privilege: &report.Privilege{ Level: level, }, } resp, err := linuxtsm.GetReport(req) if err != nil { return nil, err } // Mix the platform info in with the auxblob. extended, err := abi.ExtendedPlatformCertTable(resp.AuxBlob) if err != nil { return nil, fmt.Errorf("invalid certificate table: %v", err) } return append(resp.OutBlob, extended...), nil } // GetRawQuote returns byte format attestation plus certificate table via ConfigFS. func (p *LinuxConfigFsQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { req := &report.Request{ InBlob: reportData[:], GetAuxBlob: true, } if *defaultVMPL != "" { vmpl, err := strconv.ParseUint(*defaultVMPL, 10, 32) if err != nil { return nil, fmt.Errorf("bad default_vmpl: %q", *defaultVMPL) } req.Privilege = &report.Privilege{ Level: uint(vmpl), } } resp, err := linuxtsm.GetReport(req) if err != nil { return nil, err } // Mix the platform info in with the auxblob. extended, err := abi.ExtendedPlatformCertTable(resp.AuxBlob) if err != nil { return nil, fmt.Errorf("invalid certificate table: %v", err) } return append(resp.OutBlob, extended...), nil } // Product returns the current CPU's associated AMD SEV product information. // // Deprecated: Use ExtraPlatformInfoGUID from the cert table. func (*LinuxConfigFsQuoteProvider) Product() *spb.SevProduct { return abi.SevProduct() } // GetQuoteProvider returns a supported SEV-SNP QuoteProvider. func GetQuoteProvider() (QuoteProvider, error) { var provider QuoteProvider provider = &LinuxConfigFsQuoteProvider{} if provider.IsSupported() { return provider, nil } provider = &LinuxIoctlQuoteProvider{} if provider.IsSupported() { return provider, nil } return nil, fmt.Errorf("no supported SEV-SNP QuoteProvider found") } // GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider. func GetLeveledQuoteProvider() (LeveledQuoteProvider, error) { var provider LeveledQuoteProvider provider = &LinuxConfigFsQuoteProvider{} if provider.IsSupported() { return provider, nil } provider = &LinuxIoctlQuoteProvider{} if provider.IsSupported() { return provider, nil } return nil, fmt.Errorf("no supported SEV-SNP LeveledQuoteProvider found") } go-sev-guest-0.13.0/client/client_macos.go000066400000000000000000000050421476044460400203770ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build darwin package client import ( "fmt" spb "github.com/google/go-sev-guest/proto/sevsnp" ) // DefaultSevGuestDevicePath is the platform's usual device path to the SEV guest. const DefaultSevGuestDevicePath = "unknown" // MacOSDevice implements the Device interface with Linux ioctls. type MacOSDevice struct{} // Open is not supported on MacOS. func (*MacOSDevice) Open(_ string) error { return fmt.Errorf("MacOS is unsupported") } // OpenDevice fails on MacOS. func OpenDevice() (*MacOSDevice, error) { return nil, fmt.Errorf("MacOS is unsupported") } // Close is not supported on MacOS. func (*MacOSDevice) Close() error { return fmt.Errorf("MacOS is unsupported") } // Ioctl is not supported on MacOS. func (*MacOSDevice) Ioctl(_ uintptr, _ any) (uintptr, error) { return 0, fmt.Errorf("MacOS is unsupported") } // Product is not supported on MacOS. func (*MacOSDevice) Product() *spb.SevProduct { return &spb.SevProduct{} } // MacOSQuoteProvider implements the QuoteProvider interface with Linux's configfs-tsm. type MacOSQuoteProvider struct{} // IsSupported checks if the quote provider is supported. func (*MacOSQuoteProvider) IsSupported() bool { return false } // GetRawQuote returns byte format attestation plus certificate table via ConfigFS. func (*MacOSQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) { return nil, fmt.Errorf("MacOS is unsupported") } // GetRawQuoteAtLevel returns byte format attestation plus certificate table via ConfigFS. func (*MacOSQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]byte, error) { return nil, fmt.Errorf("MacOS is unsupported") } // GetQuoteProvider returns a supported SEV-SNP QuoteProvider. func GetQuoteProvider() (QuoteProvider, error) { return nil, fmt.Errorf("MacOS is unsupported") } // GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider. func GetLeveledQuoteProvider() (LeveledQuoteProvider, error) { return nil, fmt.Errorf("MacOS is unsupported") } go-sev-guest-0.13.0/client/client_test.go000066400000000000000000000207161476044460400202610ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "bytes" "crypto/x509" "flag" "fmt" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-sev-guest/abi" labi "github.com/google/go-sev-guest/client/linuxabi" spb "github.com/google/go-sev-guest/proto/sevsnp" test "github.com/google/go-sev-guest/testing" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/testing/protocmp" ) var devMu sync.Once var device Device var qp QuoteProvider var tests []test.TestCase var guestPolicy = flag.Uint64("guest_policy", abi.SnpPolicyToBytes(abi.SnpPolicy{SMT: true}), "If --sev_guest_device_path is not 'default', this is the policy of the VM that is running this test") // Initializing a device with key generation is expensive. Just do it once for the test suite. func initDevice() { now := time.Date(2022, time.May, 3, 9, 0, 0, 0, time.UTC) for _, tc := range test.TestCases() { // Don't test faked errors when running real hardware tests. if !UseDefaultSevGuest() && tc.WantErr != "" { continue } tests = append(tests, tc) } ones32 := make([]byte, 32) for i := range ones32 { ones32[i] = 1 } keys := map[string][]byte{ test.DerivedKeyRequestToString(&labi.SnpDerivedKeyReqABI{}): make([]byte, 32), test.DerivedKeyRequestToString(&labi.SnpDerivedKeyReqABI{GuestFieldSelect: 1}): ones32, } opts := &test.DeviceOptions{Keys: keys, Now: now} // Choose a mock device or a real device depending on the given flag. This is like testclient, // but without the circular dependency. if UseDefaultSevGuest() { sevTestDevice, err := test.TcDevice(tests, opts) if err != nil { panic(fmt.Sprintf("failed to create test device: %v", err)) } if err := sevTestDevice.Open("/dev/sev-guest"); err != nil { panic(err) } device = sevTestDevice qp = &test.QuoteProvider{Device: sevTestDevice} return } client, err := OpenDevice() if err != nil { // Unexpected panic(err) } device = client qp = &test.QuoteProvider{Device: device.(*test.Device)} } func cleanReport(report *spb.Report) { report.ReportId = make([]byte, abi.ReportIDSize) report.ReportIdMa = make([]byte, abi.ReportIDMASize) report.ChipId = make([]byte, abi.ChipIDSize) report.Measurement = make([]byte, abi.MeasurementSize) report.PlatformInfo = 0 report.CommittedTcb = 0 report.CommittedBuild = 0 report.CommittedMinor = 0 report.CommittedMajor = 0 report.CurrentTcb = 0 report.CurrentBuild = 0 report.CurrentMinor = 0 report.CurrentMajor = 0 report.LaunchTcb = 0 report.ReportedTcb = 0 } func fixReportWants(report *spb.Report) { if !UseDefaultSevGuest() { // The GCE default policy isn't the same as for the mock tests. report.Policy = *guestPolicy } } func modifyReportBytes(raw []byte, process func(report *spb.Report)) error { report, err := abi.ReportToProto(raw) if err != nil { return err } process(report) result, err := abi.ReportToAbiBytes(report) if err != nil { return err } copy(raw, result) return nil } func cleanRawReport(raw []byte) error { return modifyReportBytes(raw, cleanReport) } func fixRawReportWants(raw []byte) error { return modifyReportBytes(raw, fixReportWants) } func TestOpenGetReportClose(t *testing.T) { devMu.Do(initDevice) for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { reportProto := &spb.Report{} if err := prototext.Unmarshal([]byte(tc.OutputProto), reportProto); err != nil { t.Fatalf("test failure: %v", err) } fixReportWants(reportProto) // Does the proto report match expectations? attestation, err := GetQuoteProto(qp, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("GetReport(device, %v) = %v, %v. Want err: %v", tc.Input, attestation, err, tc.WantErr) } if tc.WantErr == "" { got := attestation.Report cleanReport(got) want := reportProto want.Signature = got.Signature // Zeros were placeholders. if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" { t.Errorf("GetReport(%v) expectation diff %s", tc.Input, diff) } } }) } } func TestOpenGetRawExtendedReportClose(t *testing.T) { devMu.Do(initDevice) for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { rawcerts, err := qp.GetRawQuote(tc.Input) if !test.Match(err, tc.WantErr) || (tc.WantErr == "" && len(rawcerts) < abi.ReportSize) { t.Fatalf("qp.GetRawQuote(%v) = %v, %v. Want err: %v", tc.Input, rawcerts, err, tc.WantErr) } if tc.WantErr == "" { raw := rawcerts[:abi.ReportSize] if err := cleanRawReport(raw); err != nil { t.Fatal(err) } got := abi.SignedComponent(raw) if err := fixRawReportWants(tc.Output[:]); err != nil { t.Fatal(err) } want := abi.SignedComponent(tc.Output[:]) if !bytes.Equal(got, want) { t.Errorf("qp.GetRawQuote(%v) = {data: %v, certs: _} want %v", tc.Input, got, want) } der, err := abi.ReportToSignatureDER(raw) if err != nil { t.Errorf("ReportToSignatureDER(%v) errored unexpectedly: %v", raw, err) } if UseDefaultSevGuest() { tcdev := device.(*test.Device) infoRaw, _ := abi.ReportSignerInfo(raw) info, _ := abi.ParseSignerInfo(infoRaw) reportSigner := tcdev.Signer.Vcek if info.SigningKey == abi.VlekReportSigner { reportSigner = tcdev.Signer.Vlek } if err := reportSigner.CheckSignature(x509.ECDSAWithSHA384, got, der); err != nil { t.Errorf("signature with test keys did not verify: %v", err) } } } }) } } func TestGetQuoteProto(t *testing.T) { devMu.Do(initDevice) for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { ereport, err := GetQuoteProto(qp, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("GetQuoteProto(qp, %v) = %v, %v. Want err: %v", tc.Input, ereport, err, tc.WantErr) } if tc.WantErr == "" { reportProto := &spb.Report{} if err := prototext.Unmarshal([]byte(tc.OutputProto), reportProto); err != nil { t.Fatalf("test failure: %v", err) } fixReportWants(reportProto) got := ereport.Report cleanReport(got) want := reportProto want.Signature = got.Signature // Zeros were placeholders. if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" { t.Errorf("GetQuoteProto(qp, %v) = {data: %v, certs: _} want %v. Diff: %s", tc.Input, got, want, diff) } if UseDefaultSevGuest() { tcdev := device.(*test.Device) if !bytes.Equal(ereport.GetCertificateChain().GetArkCert(), tcdev.Signer.Ark.Raw) { t.Errorf("ARK certificate mismatch. Got %v, want %v", ereport.GetCertificateChain().GetArkCert(), tcdev.Signer.Ark.Raw) } if !bytes.Equal(ereport.GetCertificateChain().GetAskCert(), tcdev.Signer.Ask.Raw) { t.Errorf("ASK certificate mismatch. Got %v, want %v", ereport.GetCertificateChain().GetAskCert(), tcdev.Signer.Ask.Raw) } if !bytes.Equal(ereport.GetCertificateChain().GetVcekCert(), tcdev.Signer.Vcek.Raw) { t.Errorf("VCEK certificate mismatch. Got %v, want %v", ereport.GetCertificateChain().GetVcekCert(), tcdev.Signer.Vcek.Raw) } } } }) } } func TestGetDerivedKey(t *testing.T) { devMu.Do(initDevice) key1, err := GetDerivedKeyAcknowledgingItsLimitations(device, &SnpDerivedKeyReq{ UseVCEK: true, }) if err != nil { t.Fatalf("Could not get key1: %v", err) } key2, err := GetDerivedKeyAcknowledgingItsLimitations(device, &SnpDerivedKeyReq{ UseVCEK: true, GuestFieldSelect: GuestFieldSelect{ GuestPolicy: true, }, }) if err != nil { t.Fatalf("Could not get key2: %v", err) } key3, err := GetDerivedKeyAcknowledgingItsLimitations(device, &SnpDerivedKeyReq{ UseVCEK: true, }) if err != nil { t.Fatalf("Could not get key3: %v", err) } if bytes.Equal(key1.Data[:], key2.Data[:]) { t.Errorf("GetDerivedKey...(nothing) = %v = GetDerivedKey...(guestPolicy) = %v", key1.Data, key2.Data) } if !bytes.Equal(key1.Data[:], key3.Data[:]) { t.Errorf("GetDerivedKey...(nothing) = %v and %v. Expected equality", key1.Data, key3.Data) } } go-sev-guest-0.13.0/client/client_windows.go000066400000000000000000000050231476044460400207660ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package client import ( "fmt" spb "github.com/google/go-sev-guest/proto/sevsnp" ) // WindowsDevice implements the Device interface with Linux ioctls. type WindowsDevice struct{} // Open is not supported on Windows. func (*WindowsDevice) Open(_ string) error { return fmt.Errorf("Windows is unsupported") } // OpenDevice fails on Windows. func OpenDevice() (*WindowsDevice, error) { return nil, fmt.Errorf("Windows is unsupported") } // Close is not supported on Windows. func (*WindowsDevice) Close() error { return fmt.Errorf("Windows is unsupported") } // Ioctl is not supported on Windows. func (*WindowsDevice) Ioctl(_ uintptr, _ any) (uintptr, error) { // The GuestAttestation library on Windows is closed source. return 0, fmt.Errorf("Windows is unsupported") } // Product is not supported on Windows. func (*WindowsDevice) Product() *spb.SevProduct { return &spb.SevProduct{} } // WindowsQuoteProvider implements the QuoteProvider interface with Linux's configfs-tsm. type WindowsQuoteProvider struct{} // IsSupported checks if the quote provider is supported. func (*WindowsQuoteProvider) IsSupported() bool { return false } // GetRawQuote returns byte format attestation plus certificate table via ConfigFS. func (*WindowsQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) { return nil, fmt.Errorf("Windows is unsupported") } // GetRawQuoteAtLevel returns byte format attestation plus certificate table via ConfigFS. func (*WindowsQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]byte, error) { return nil, fmt.Errorf("Windows is unsupported") } // GetQuoteProvider returns a supported SEV-SNP QuoteProvider. func GetQuoteProvider() (QuoteProvider, error) { return nil, fmt.Errorf("Windows is unsupported") } // GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider. func GetLeveledQuoteProvider() (LeveledQuoteProvider, error) { return nil, fmt.Errorf("Windows is unsupported") } go-sev-guest-0.13.0/client/linuxabi/000077500000000000000000000000001476044460400172225ustar00rootroot00000000000000go-sev-guest-0.13.0/client/linuxabi/linux_abi.go000066400000000000000000000247121476044460400215310ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package linuxabi describes the /dev/sev-guest ioctl command ABI. package linuxabi import ( "errors" "fmt" "reflect" "unsafe" ) // EsResult is the status code type for Linux's GHCB communication results. type EsResult int // ioctl bits for x86-64 const ( iocNrbits = 8 iocTypebits = 8 iocSizebits = 14 iocDirbits = 2 iocNrshift = 0 iocTypeshift = (iocNrshift + iocNrbits) iocSizeshift = (iocTypeshift + iocTypebits) iocDirshift = (iocSizeshift + iocSizebits) iocWrite = 1 iocRead = 2 // Linux /dev/sev-guest ioctl interface iocTypeSnpGuestReq = 'S' iocSnpWithoutNr = ((iocWrite | iocRead) << iocDirshift) | (iocTypeSnpGuestReq << iocTypeshift) | // unsafe.Sizeof(snpUserGuestRequest) (32 << iocSizeshift) // IocSnpGetReport is the ioctl command for getting an attestation report IocSnpGetReport = iocSnpWithoutNr | (0x0 << iocNrshift) // IocSnpGetDerivedKey is the ioctl command for getting a key derived from measured components and // either the VCEK or VMRK. IocSnpGetDerivedKey = iocSnpWithoutNr | (0x1 << iocNrshift) // IocSnpGetReport is the ioctl command for getting an extended attestation report that includes // certificate information. IocSnpGetExtendedReport = iocSnpWithoutNr | (0x2 << iocNrshift) // The message version for MSG_REPORT_REQ in the SNP API. Specified as 1. guestMsgVersion = 1 // These numbers are from the uapi header sev_guest.h snpResportRespSize = 4000 msgReportReqHeaderSize = 0x20 SnpReportRespReportSize = snpResportRespSize - msgReportReqHeaderSize ) const ( // EsOk denotes success. EsOk EsResult = iota // EsUnsupported denotes that the requested operation is not supported. EsUnsupported // EsVmmError denotes that the virtual machine monitor was in an unexpected state. EsVmmError // EsDecodeFailed denotes that instruction decoding failed. EsDecodeFailed // EsException denotes that the GHCB communication caused an exception. EsException // EsRetry is the code for a retry instruction emulation EsRetry ) // SevEsErr is an error that interprets SEV-ES guest-host communication results. type SevEsErr struct { Result EsResult } func (err *SevEsErr) Error() string { if err.Result == EsUnsupported { return "requested operation not supported" } if err.Result == EsVmmError { return "unexpected state from the VMM" } if err.Result == EsDecodeFailed { return "instruction decoding failed" } if err.Result == EsException { return "instruction caused exception" } if err.Result == EsRetry { return "retry instruction emulation" } return "unknown error" } // SnpReportReqABI is Linux's sev-guest ioctl abi for sending a GET_REPORT request. See // include/uapi/linux/sev-guest.h type SnpReportReqABI struct { // ReportData to be included in the report ReportData [64]uint8 // Vmpl is the SEV-SNP VMPL level to be included in the report. // The kernel must have access to the corresponding VMPCK. Vmpl uint32 reserved [28]byte } // SnpReportRespABI is Linux's sev-guest ioctl abi for receiving a GET_REPORT response. // The size is expected to be snpReportRespSize. type SnpReportRespABI struct { Status uint32 ReportSize uint32 reserved [0x20 - 8]byte // Data is the response data, see SEV-SNP spec for the format Data [SnpReportRespReportSize]uint8 } // ABI returns the same object since it doesn't need a separate representation across the interface. func (r *SnpReportReqABI) ABI() BinaryConversion { return r } // Pointer returns a pointer to the object itself. func (r *SnpReportReqABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish is a no-op. func (r *SnpReportReqABI) Finish(_ BinaryConvertible) error { return nil } // ABI returns the same object since it doesn't need a separate representation across the interface. func (r *SnpReportRespABI) ABI() BinaryConversion { return r } // Pointer returns a pointer to the object itself. func (r *SnpReportRespABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish checks the status of the message and translates it to a Golang error. func (r *SnpReportRespABI) Finish(_ BinaryConvertible) error { if r.Status != 0 { switch r.Status { case 0x16: // Value from MSG_REPORT_RSP specification in SNP API. return errors.New("get_report had invalid parameters") default: return fmt.Errorf("unknown status: 0x%x", r.Status) } } return nil } // SnpDerivedKeyReqABI is the ABI representation of a request to the SEV guest device to derive a // key from specified information. type SnpDerivedKeyReqABI struct { // RootKeySelect is all reserved bits except bit 0 for UseVMRK (1) or UseVCEK (0). RootKeySelect uint32 reserved uint32 GuestFieldSelect uint64 // Vmpl to mix into the key. Must be greater than or equal to current Vmpl. Vmpl uint32 // GuestSVN to mix into the key. Must be less than or equal to GuestSVN at launch. GuestSVN uint32 // TCBVersion to mix into the key. Must be less than or equal to the CommittedTcb. TCBVersion uint64 } // Pointer returns a pointer to the object. func (r *SnpDerivedKeyReqABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish is a no-op. func (r *SnpDerivedKeyReqABI) Finish(BinaryConvertible) error { return nil } // ABI returns the ABI representation of this object. func (r *SnpDerivedKeyReqABI) ABI() BinaryConversion { return r } // SnpDerivedKeyRespABI represents the response to an SnpDerivedKeyReq. type SnpDerivedKeyRespABI struct { Status uint32 reserved [0x20 - 4]byte Data [32]byte } // ABI returns the object itself. func (r *SnpDerivedKeyRespABI) ABI() BinaryConversion { return r } // Pointer returns a pointer to the object itself. func (r *SnpDerivedKeyRespABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish is a no-op. func (r *SnpDerivedKeyRespABI) Finish(BinaryConvertible) error { switch r.Status { case 0: return nil case 0x16: return errors.New("msg_key_req error: invalid parameters") default: return fmt.Errorf("msg_key_req unknown status code: 0x%x", r.Status) } } // SnpExtendedReportReqABI is Linux's sev-guest ioctl abi for sending a GET_EXTENDED_REPORT request. type SnpExtendedReportReqABI struct { Data SnpReportReqABI // Where to copy the certificate blob. CertsAddress unsafe.Pointer // length of the certificate blob CertsLength uint32 } // SnpExtendedReportReq is close to Linux's sev-guest ioctl abi for sending a GET_EXTENDED_REPORT request, // but uses safer types for the Ioctl interface. type SnpExtendedReportReq struct { Data SnpReportReqABI // Certs receives the certificate blob after the extended report request. Certs []byte // CertsLength is the length of the certificate blob. CertsLength uint32 } // Pointer returns a pointer so the object itself. func (r *SnpExtendedReportReqABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish writes back the changed CertsLength value. func (r *SnpExtendedReportReqABI) Finish(b BinaryConvertible) error { s, ok := b.(*SnpExtendedReportReq) if !ok { return fmt.Errorf("Finish argument is %v. Expects a *SnpExtendedReportReq", reflect.TypeOf(b)) } s.CertsLength = r.CertsLength return nil } // ABI returns an object that can cross the ABI boundary and copy back changes to the original // object. func (r *SnpExtendedReportReq) ABI() BinaryConversion { var certsAddress unsafe.Pointer if len(r.Certs) != 0 { certsAddress = unsafe.Pointer(&r.Certs[0]) } return &SnpExtendedReportReqABI{ Data: r.Data, CertsAddress: certsAddress, CertsLength: r.CertsLength, } } // SnpUserGuestRequestABI is Linux's sev-guest ioctl abi for issuing a guest message. type SnpUserGuestRequestABI struct { GuestMsgVersion uint32 // Request and response structure address. ReqData unsafe.Pointer RespData unsafe.Pointer // firmware error code on failure (see psp-sev.h in Linux kernel) FwErr uint64 } type snpUserGuestRequestConversion struct { abi SnpUserGuestRequestABI reqConv BinaryConversion respConv BinaryConversion } // SnpUserGuestRequest is Linux's sev-guest ioctl interface for issuing a guest message. The // types here enhance runtime safety when using Ioctl as an interface. type SnpUserGuestRequest struct { // Request and response structure address. ReqData BinaryConvertible RespData BinaryConvertible // firmware error code on failure (see psp-sev.h in Linux kernel) FwErr uint64 } // ABI returns an object that can cross the ABI boundary and copy back changes to the original // object. func (r *SnpUserGuestRequest) ABI() BinaryConversion { result := &snpUserGuestRequestConversion{ reqConv: r.ReqData.ABI(), respConv: r.RespData.ABI(), } result.abi.GuestMsgVersion = guestMsgVersion result.abi.ReqData = result.reqConv.Pointer() result.abi.RespData = result.respConv.Pointer() return result } // Pointer returns a pointer to the object that crosses the ABI boundary. func (r *snpUserGuestRequestConversion) Pointer() unsafe.Pointer { return unsafe.Pointer(&r.abi) } // Finish writes back the FwErr and any changes to the request or response objects. func (r *snpUserGuestRequestConversion) Finish(b BinaryConvertible) error { s, ok := b.(*SnpUserGuestRequest) if !ok { return fmt.Errorf("Finish argument is %v. Expects a *SnpUserGuestRequestSafe", reflect.TypeOf(b)) } if err := r.reqConv.Finish(s.ReqData); err != nil { return fmt.Errorf("could not finalize request data: %v", err) } if err := r.respConv.Finish(s.RespData); err != nil { return fmt.Errorf("could not finalize response data: %v", err) } s.FwErr = r.abi.FwErr return nil } // BinaryConversion is an interface that abstracts a "stand-in" object that passes through an ABI // boundary and can finalize changes to the original object. type BinaryConversion interface { Pointer() unsafe.Pointer Finish(BinaryConvertible) error } // BinaryConvertible is an interface for an object that can produce a partner BinaryConversion // object to allow its representation to pass the ABI boundary. type BinaryConvertible interface { ABI() BinaryConversion } go-sev-guest-0.13.0/go.mod000066400000000000000000000006601476044460400152410ustar00rootroot00000000000000module github.com/google/go-sev-guest go 1.19 require ( github.com/golang/protobuf v1.5.0 github.com/google/go-cmp v0.5.7 github.com/google/go-configfs-tsm v0.2.2 github.com/google/logger v1.1.1 github.com/google/uuid v1.6.0 go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.17.0 golang.org/x/sys v0.15.0 google.golang.org/protobuf v1.33.0 ) require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect go-sev-guest-0.13.0/go.sum000066400000000000000000000044251476044460400152710ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= go-sev-guest-0.13.0/kds/000077500000000000000000000000001476044460400147125ustar00rootroot00000000000000go-sev-guest-0.13.0/kds/kds.go000066400000000000000000000717241476044460400160350ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package kds defines values specified for the AMD Key Distribution Service. package kds import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/hex" "encoding/pem" "fmt" "net/url" "strconv" "strings" "github.com/google/go-sev-guest/abi" pb "github.com/google/go-sev-guest/proto/sevsnp" "go.uber.org/multierr" "google.golang.org/protobuf/types/known/wrapperspb" ) // Encapsulates the rest of the fields after AMD's V{C,L}EK OID classifier prefix 1.3.6.1.4.1.3704.1. type kdsOID struct { major int minor int } var ( // OidStructVersion is the x509v3 extension for V[CL]EK certificate struct version. OidStructVersion = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 1}) // OidProductName1 is the x509v3 extension for V[CL]EK certificate product name. OidProductName1 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 2}) // OidBlSpl is the x509v3 extension for V[CL]EK certificate bootloader security patch level. OidBlSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 1}) // OidTeeSpl is the x509v3 extension for V[CL]EK certificate TEE security patch level. OidTeeSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 2}) // OidSnpSpl is the x509v3 extension for V[CL]EK certificate SNP security patch level. OidSnpSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 3}) // OidSpl4 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl4 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 4}) // OidSpl5 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl5 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 5}) // OidSpl6 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl6 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 6}) // OidSpl7 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl7 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 7}) // OidUcodeSpl is the x509v3 extension for V[CL]EK microcode security patch level. OidUcodeSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 8}) // OidHwid is the x509v3 extension for VCEK certificate associated hardware identifier. OidHwid = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 4}) // OidCspID is the x509v3 extension for a VLEK certificate's Cloud Service Provider's // origin TLS key's certificate's subject key's CommonName. OidCspID = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 5}) authorityKeyOid = asn1.ObjectIdentifier([]int{2, 5, 29, 35}) // Short forms of the asn1 Object identifiers to use in map lookups, since []int are invalid key // types. kdsStructVersion = kdsOID{major: 1} kdsProductName1 = kdsOID{major: 2} kdsBlSpl = kdsOID{major: 3, minor: 1} kdsTeeSpl = kdsOID{major: 3, minor: 2} kdsSnpSpl = kdsOID{major: 3, minor: 3} kdsSpl4 = kdsOID{major: 3, minor: 4} kdsSpl5 = kdsOID{major: 3, minor: 5} kdsSpl6 = kdsOID{major: 3, minor: 6} kdsSpl7 = kdsOID{major: 3, minor: 7} kdsUcodeSpl = kdsOID{major: 3, minor: 8} kdsHwid = kdsOID{major: 4} kdsCspID = kdsOID{major: 5} kdsHostname = "kdsintf.amd.com" kdsBaseURL = "https://" + kdsHostname kdsVcekPath = "/vcek/v1/" kdsVlekPath = "/vlek/v1/" uint0 = &wrapperspb.UInt32Value{Value: 0} uint1 = &wrapperspb.UInt32Value{Value: 1} uint2 = &wrapperspb.UInt32Value{Value: 2} // Chip manufacturers assign stepping versions strings that are // to describe a stepping number for a particular model chip. There is no way // other than documentation to map a stepping number to a stepping version and // vice versa. steppingDecoder = map[string]*pb.SevProduct{ "Milan-B0": {Name: pb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: uint0}, "Milan-B1": {Name: pb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: uint1}, "Genoa-B0": {Name: pb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: uint0}, "Genoa-B1": {Name: pb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: uint1}, "Genoa-B2": {Name: pb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: uint2}, "Turin-B0": {Name: pb.SevProduct_SEV_PRODUCT_TURIN, MachineStepping: uint0}, "Turin-B1": {Name: pb.SevProduct_SEV_PRODUCT_TURIN, MachineStepping: uint1}, } milanSteppingVersions = []string{"B0", "B1"} genoaSteppingVersions = []string{"B0", "B1", "B2"} turinSteppingVersions = []string{"B0", "B1"} // ProductLineCpuid associates the CPUID_1_EAX value (Stepping 0) to its AMD product name. ProductLineCpuid = map[uint32]string{ 0x00a00f10: "Milan", 0x00a10f10: "Genoa", 0x00b00f20: "Turin", } ) // TCBVersion is a 64-bit bitfield of different security patch levels of AMD firmware and microcode. type TCBVersion uint64 // Extensions represents the information stored in the KDS-specified x509 extensions of a V{C,L}EK // certificate. type Extensions struct { StructVersion uint8 ProductName string // The host driver knows the difference between primary and secondary HWID. // Primary vs secondary is irrelevant to verification. Must be nil or // abi.ChipIDSize long. HWID []byte TCBVersion TCBVersion CspID string } func oidTokdsOID(id asn1.ObjectIdentifier) (kdsOID, error) { if id.Equal(OidStructVersion) { return kdsStructVersion, nil } if id.Equal(OidProductName1) { return kdsProductName1, nil } if id.Equal(OidBlSpl) { return kdsBlSpl, nil } if id.Equal(OidHwid) { return kdsHwid, nil } if id.Equal(OidTeeSpl) { return kdsTeeSpl, nil } if id.Equal(OidSnpSpl) { return kdsSnpSpl, nil } if id.Equal(OidSpl4) { return kdsSpl4, nil } if id.Equal(OidSpl5) { return kdsSpl5, nil } if id.Equal(OidSpl6) { return kdsSpl6, nil } if id.Equal(OidSpl7) { return kdsSpl7, nil } if id.Equal(OidUcodeSpl) { return kdsUcodeSpl, nil } if id.Equal(OidCspID) { return kdsCspID, nil } return kdsOID{}, fmt.Errorf("not an AMD KDS OID: %v", id) } func kdsOidMap(cert *x509.Certificate) (map[kdsOID]*pkix.Extension, error) { result := make(map[kdsOID]*pkix.Extension) for i, ext := range cert.Extensions { if ext.Id.Equal(authorityKeyOid) { // Since ASK is a CA, signing can impart the authority key extension. continue } oid, err := oidTokdsOID(ext.Id) if err != nil { return nil, err } if _, ok := result[oid]; ok { return nil, fmt.Errorf("duplicate AMD KDS extension: %v", ext) } result[oid] = &cert.Extensions[i] } return result, nil } // TCBParts represents all TCB field values in a given uint64 representation of // an AMD secure processor firmware TCB version. type TCBParts struct { // BlSpl is the bootloader security patch level. BlSpl uint8 // TeeSpl is the TEE security patch level. TeeSpl uint8 // Spl4 is reserved. Spl4 uint8 // Spl5 is reserved. Spl5 uint8 // Spl6 is reserved. Spl6 uint8 // Spl7 is reserved. Spl7 uint8 // SnpSpl is the SNP security patch level. SnpSpl uint8 // UcodeSpl is the microcode security patch level. UcodeSpl uint8 } // ComposeTCBParts returns an SEV-SNP TCB_VERSION from OID mapping values. The spl4-spl7 fields are // reserved, but the KDS specification designates them as 4 byte-sized fields. func ComposeTCBParts(parts TCBParts) (TCBVersion, error) { // Only UcodeSpl may be 0-255. All others must be 0-127. check127 := func(name string, value uint8) error { if value > 127 { return fmt.Errorf("%s TCB part is %d. Expect 0-127", name, value) } return nil } if err := multierr.Combine(check127("SnpSpl", parts.SnpSpl), check127("Spl7", parts.Spl7), check127("Spl6", parts.Spl6), check127("Spl5", parts.Spl5), check127("Spl4", parts.Spl4), check127("TeeSpl", parts.TeeSpl), check127("BlSpl", parts.BlSpl), ); err != nil { return TCBVersion(0), err } return TCBVersion( (uint64(parts.UcodeSpl) << 56) | (uint64(parts.SnpSpl) << 48) | (uint64(parts.Spl7) << 40) | (uint64(parts.Spl6) << 32) | (uint64(parts.Spl5) << 24) | (uint64(parts.Spl4) << 16) | (uint64(parts.TeeSpl) << 8) | (uint64(parts.BlSpl) << 0)), nil } // DecomposeTCBVersion interprets the byte components of the AMD representation of the // platform security patch levels into a struct. func DecomposeTCBVersion(tcb TCBVersion) TCBParts { return TCBParts{ UcodeSpl: uint8((uint64(tcb) >> 56) & 0xff), SnpSpl: uint8((uint64(tcb) >> 48) & 0xff), Spl7: uint8((uint64(tcb) >> 40) & 0xff), Spl6: uint8((uint64(tcb) >> 32) & 0xff), Spl5: uint8((uint64(tcb) >> 24) & 0xff), Spl4: uint8((uint64(tcb) >> 16) & 0xff), TeeSpl: uint8((uint64(tcb) >> 8) & 0xff), BlSpl: uint8((uint64(tcb) >> 0) & 0xff), } } // TCBPartsLE returns true iff all TCB components of tcb0 are <= the corresponding tcb1 components. func TCBPartsLE(tcb0, tcb1 TCBParts) bool { return (tcb0.UcodeSpl <= tcb1.UcodeSpl) && (tcb0.SnpSpl <= tcb1.SnpSpl) && (tcb0.Spl7 <= tcb1.Spl7) && (tcb0.Spl6 <= tcb1.Spl6) && (tcb0.Spl5 <= tcb1.Spl5) && (tcb0.Spl4 <= tcb1.Spl4) && (tcb0.TeeSpl <= tcb1.TeeSpl) && (tcb0.BlSpl <= tcb1.BlSpl) } func asn1U8(ext *pkix.Extension, field string, out *uint8) error { if ext == nil { return fmt.Errorf("no extension for field %s", field) } var i int rest, err := asn1.Unmarshal(ext.Value, &i) if err != nil { return fmt.Errorf("could not parse extension as an integer %v: %v", *ext, err) } // Check that i is a valid uint8 value. if len(rest) != 0 { return fmt.Errorf("unexpected leftover bytes for U8 field %s", field) } if i < 0 || i > 255 { return fmt.Errorf("int value for field %s isn't a uint8: %d", field, i) } *out = uint8(i) return nil } func asn1IA5String(ext *pkix.Extension, field string, out *string) error { if ext == nil || len(ext.Value) == 0 { return fmt.Errorf("no extension for field %s", field) } // Even with the "ia5" params, Unmarshal is too lax about string tags. if ext.Value[0] != asn1.TagIA5String { return fmt.Errorf("value is not tagged as an IA5String: %d", ext.Value[0]) } rest, err := asn1.UnmarshalWithParams(ext.Value, out, "ia5") if err != nil { return fmt.Errorf("could not parse extension as an IA5String %v: %v", *ext, err) } if len(rest) != 0 { return fmt.Errorf("unexpected leftover bytes for IA5String field %s", field) } return nil } func asn1OctetString(ext *pkix.Extension, field string, size int) ([]byte, error) { if ext == nil { return nil, fmt.Errorf("no extension for field %s", field) } // ASN1 requires a type tag, but for some reason the KDS doesn't add that for the HWID. if len(ext.Value) == size { return ext.Value, nil } // In case AMD adds the type and the value's length increases to include the type tag, then try // to unmarshal here. var octet []byte rest, err := asn1.Unmarshal(ext.Value, &octet) if err != nil { return nil, fmt.Errorf("could not parse extension as an octet string %v (value %v): %v", *ext, ext.Value, err) } if len(rest) != 0 { return nil, fmt.Errorf("expected leftover bytes in extension value for field %v", field) } // Check the expected length. if size >= 0 && len(octet) != size { return nil, fmt.Errorf("size is %d, expected %d", len(octet), size) } return octet, nil } func kdsOidMapToExtensions(exts map[kdsOID]*pkix.Extension) (*Extensions, error) { var result Extensions if err := asn1U8(exts[kdsStructVersion], "StructVersion", &result.StructVersion); err != nil { return nil, err } if err := asn1IA5String(exts[kdsProductName1], "ProductName1", &result.ProductName); err != nil { return nil, err } hwidExt, ok := exts[kdsHwid] if ok { octet, err := asn1OctetString(hwidExt, "HWID", 64) if err != nil { return nil, err } result.HWID = octet } cspidExt := exts[kdsCspID] if cspidExt != nil { if err := asn1IA5String(cspidExt, "CSP_ID", &result.CspID); err != nil { return nil, err } if hwidExt != nil { return nil, fmt.Errorf("certificate has both HWID (%s) and CSP_ID (%s) extensions", hex.EncodeToString(result.HWID), result.CspID) } } var blspl, snpspl, teespl, spl4, spl5, spl6, spl7, ucodespl uint8 if err := asn1U8(exts[kdsBlSpl], "BlSpl", &blspl); err != nil { return nil, err } if err := asn1U8(exts[kdsTeeSpl], "TeeSpl", &teespl); err != nil { return nil, err } if err := asn1U8(exts[kdsSnpSpl], "SnpSpl", &snpspl); err != nil { return nil, err } if err := asn1U8(exts[kdsSpl4], "Spl4", &spl4); err != nil { return nil, err } if err := asn1U8(exts[kdsSpl5], "Spl5", &spl5); err != nil { return nil, err } if err := asn1U8(exts[kdsSpl6], "Spl6", &spl6); err != nil { return nil, err } if err := asn1U8(exts[kdsSpl7], "Spl7", &spl7); err != nil { return nil, err } if err := asn1U8(exts[kdsUcodeSpl], "UcodeSpl", &ucodespl); err != nil { return nil, err } tcb, err := ComposeTCBParts(TCBParts{ BlSpl: blspl, SnpSpl: snpspl, TeeSpl: teespl, Spl4: spl4, Spl5: spl5, Spl6: spl6, Spl7: spl7, UcodeSpl: ucodespl, }) if err != nil { return nil, err } result.TCBVersion = tcb return &result, nil } // preEndorsementKeyCertificateExtensions returns the x509v3 extensions from the KDS specification interpreted // into a struct type for either the VCEK or the VLEK func preEndorsementKeyCertificateExtensions(cert *x509.Certificate) (*Extensions, error) { oidMap, err := kdsOidMap(cert) if err != nil { return nil, err } extensions, err := kdsOidMapToExtensions(oidMap) if err != nil { return nil, err } return extensions, nil } // VcekCertificateExtensions returns the x509v3 extensions from the KDS specification of a VCEK // certificate interpreted into a struct type. func VcekCertificateExtensions(cert *x509.Certificate) (*Extensions, error) { if cert == nil { return nil, fmt.Errorf("cert cannot be nil") } exts, err := preEndorsementKeyCertificateExtensions(cert) if err != nil { return nil, err } if exts.CspID != "" { return nil, fmt.Errorf("unexpected CSP_ID in VCEK certificate: %s", exts.CspID) } if len(exts.HWID) != abi.ChipIDSize { return nil, fmt.Errorf("missing HWID extension for VCEK certificate") } return exts, nil } // VlekCertificateExtensions returns the x509v3 extensions from the KDS specification of a VLEK // certificate interpreted into a struct type. func VlekCertificateExtensions(cert *x509.Certificate) (*Extensions, error) { if cert == nil { return nil, fmt.Errorf("cert cannot be nil") } exts, err := preEndorsementKeyCertificateExtensions(cert) if err != nil { return nil, err } if exts.CspID == "" { return nil, fmt.Errorf("missing CSP_ID in VLEK certificate") } if exts.HWID != nil { return nil, fmt.Errorf("unexpected HWID in VLEK certificate: %s", hex.EncodeToString(exts.HWID)) } return exts, nil } // CertificateExtensions returns the x509v3 extensions from the KDS specification interpreted // into a struct type. func CertificateExtensions(cert *x509.Certificate, key abi.ReportSigner) (*Extensions, error) { switch key { case abi.VcekReportSigner: return VcekCertificateExtensions(cert) case abi.VlekReportSigner: return VlekCertificateExtensions(cert) case abi.NoneReportSigner: return &Extensions{}, nil } return nil, fmt.Errorf("unexpected endorsement key kind %v", key) } // ParseProductCertChain returns the DER-formatted certificates represented by the body // of the ProductCertChain (cert_chain) endpoint, ASK and ARK in that order. func ParseProductCertChain(pems []byte) ([]byte, []byte, error) { checkForm := func(name string, b *pem.Block) error { if b == nil { return fmt.Errorf("could not find %s PEM block", name) } if b.Type != "CERTIFICATE" { return fmt.Errorf("the %s PEM block type is %s. Expect CERTIFICATE", name, b.Type) } if len(b.Headers) != 0 { return fmt.Errorf("the %s PEM block has non-empty headers: %v", name, b.Headers) } return nil } askBlock, arkRest := pem.Decode(pems) arkBlock, noRest := pem.Decode(arkRest) if err := multierr.Combine(checkForm("ASK or ASVK", askBlock), checkForm("ARK", arkBlock)); err != nil { return nil, nil, err } if len(noRest) != 0 { return nil, nil, fmt.Errorf("unexpected trailing bytes: %d bytes", len(noRest)) } return askBlock.Bytes, arkBlock.Bytes, nil } // productBaseURL returns the base URL for all certificate queries within a particular product for the // given report signer kind. func productBaseURL(s abi.ReportSigner, name string) string { path := "unknown" if s == abi.VcekReportSigner { path = kdsVcekPath } if s == abi.VlekReportSigner { path = kdsVlekPath } return fmt.Sprintf("%s%s%s", kdsBaseURL, path, name) } // ProductCertChainURL returns the AMD KDS URL for retrieving the ARK and AS(V)K // certificates on the given product in ??? format. func ProductCertChainURL(s abi.ReportSigner, productLine string) string { return fmt.Sprintf("%s/cert_chain", productBaseURL(s, productLine)) } // VCEKCertURL returns the AMD KDS URL for retrieving the VCEK on a given product // at a given TCB version. The hwid is the CHIP_ID field in an attestation report. func VCEKCertURL(productLine string, hwid []byte, tcb TCBVersion) string { parts := DecomposeTCBVersion(tcb) return fmt.Sprintf("%s/%s?blSPL=%d&teeSPL=%d&snpSPL=%d&ucodeSPL=%d", productBaseURL(abi.VcekReportSigner, productLine), hex.EncodeToString(hwid), parts.BlSpl, parts.TeeSpl, parts.SnpSpl, parts.UcodeSpl, ) } // VLEKCertURL returns the GET URL for retrieving a VLEK certificate, but without the necessary // CSP secret in the HTTP headers that makes the request validate to the KDS. func VLEKCertURL(productLine string, tcb TCBVersion) string { parts := DecomposeTCBVersion(tcb) return fmt.Sprintf("%s/cert?blSPL=%d&teeSPL=%d&snpSPL=%d&ucodeSPL=%d", productBaseURL(abi.VlekReportSigner, productLine), parts.BlSpl, parts.TeeSpl, parts.SnpSpl, parts.UcodeSpl, ) } // VCEKCert represents the attestation report components represented in a KDS VCEK certificate // request URL. type VCEKCert struct { // Product is the product string (no stepping value) present in the VCEK cert url // // Deprecated: Use ProductLine. Product string ProductLine string HWID []byte TCB uint64 } // VCEKCertProduct returns a VCEKCert with the product line set to productLine. func VCEKCertProduct(productLine string) VCEKCert { return VCEKCert{ Product: productLine, // TODO(Issue#114): Remove ProductLine: productLine, } } // VLEKCert represents the attestation report components represented in a KDS VLEK certificate // request URL. type VLEKCert struct { // Product is the product string (no stepping value) present in the VCEK cert url // // Deprecated: Use ProductLine. Product string ProductLine string TCB uint64 } // CertFunction is an enumeration of which endorsement key type is getting certified. type CertFunction int const ( // UnknownCertFunction represents an unknown endpoint for parsing KDS URLs. UnknownCertFunction CertFunction = iota // VcekCertFunction represents the vcek endpoints for parsing KDS URLs. VcekCertFunction // VlekCertFunction represents the vlek endpoints for parsing KDS URLs. VlekCertFunction ) type parsedURL struct { productLine string simpleURL *url.URL function CertFunction } // parseBaseProductURL returns the product name for a root certificate chain URL if it is one, // with the parsed URL that has the product prefix trimmed. func parseBaseProductURL(kdsurl string) (*parsedURL, error) { u, err := url.Parse(kdsurl) if err != nil { return nil, fmt.Errorf("invalid AMD KDS URL %q: %v", kdsurl, err) } if u.Scheme != "https" { return nil, fmt.Errorf("unexpected AMD KDS URL scheme %q, want \"https\"", u.Scheme) } if u.Host != kdsHostname { return nil, fmt.Errorf("unexpected AMD KDS URL host %q, want %q", u.Host, kdsHostname) } result := &parsedURL{} vcekFunc := strings.HasPrefix(u.Path, kdsVcekPath) vlekFunc := strings.HasPrefix(u.Path, kdsVlekPath) var function string if vcekFunc { function = strings.TrimPrefix(u.Path, kdsVcekPath) result.function = VcekCertFunction } else if vlekFunc { function = strings.TrimPrefix(u.Path, kdsVlekPath) result.function = VlekCertFunction } else { return nil, fmt.Errorf("unexpected AMD KDS URL path %q, want prefix %q or %q", u.Path, kdsVcekPath, kdsVlekPath) } // The following should be product/endpoint pieces := strings.Split(function, "/") if len(pieces) != 2 { return nil, fmt.Errorf("url has unexpected endpoint %q not product/endpoint", function) } result.productLine = pieces[0] // Set the URL's path to the rest of the path without the API or product prefix. u.Path = pieces[1] result.simpleURL = u return result, nil } // ParseProductCertChainURL returns the product name and either "vcek" or "vlek" for a KDS // cert_chain url, or an error if the input is not a KDS cert_chain url. func ParseProductCertChainURL(kdsurl string) (string, CertFunction, error) { parsed, err := parseBaseProductURL(kdsurl) if err != nil { return "", UnknownCertFunction, err } if parsed.simpleURL.Path != "cert_chain" { return "", UnknownCertFunction, fmt.Errorf("unexpected AMD KDS URL path %q, want \"cert_chain\"", parsed.simpleURL.Path) } return parsed.productLine, parsed.function, nil } func parseTCBURL(u *url.URL) (uint64, error) { values, err := url.ParseQuery(u.RawQuery) if err != nil { return 0, fmt.Errorf("invalid AMD KDS URL query %q: %v", u.RawQuery, err) } parts := TCBParts{} for key, valuelist := range values { var setter func(number uint8) switch key { case "blSPL": setter = func(number uint8) { parts.BlSpl = number } case "teeSPL": setter = func(number uint8) { parts.TeeSpl = number } case "snpSPL": setter = func(number uint8) { parts.SnpSpl = number } case "ucodeSPL": setter = func(number uint8) { parts.UcodeSpl = number } default: return 0, fmt.Errorf("unexpected KDS TCB version URL argument %q", key) } for _, val := range valuelist { number, err := strconv.Atoi(val) if err != nil || number < 0 || number > 255 { return 0, fmt.Errorf("invalid KDS TCB version URL argument value %q, want a value 0-255", val) } setter(uint8(number)) } } tcb, err := ComposeTCBParts(parts) if err != nil { return 0, fmt.Errorf("invalid AMD KDS TCB arguments: %v", err) } return uint64(tcb), err } // ParseVCEKCertURL returns the attestation report components represented in the given KDS VCEK // certificate request URL. func ParseVCEKCertURL(kdsurl string) (VCEKCert, error) { result := VCEKCert{} parsed, err := parseBaseProductURL(kdsurl) if err != nil { return result, err } if parsed.function != VcekCertFunction { return result, fmt.Errorf("not a VCEK certificate URL: %s", kdsurl) } result.Product = parsed.productLine // TODO(Issue#114): Remove. result.ProductLine = parsed.productLine hwid, err := hex.DecodeString(parsed.simpleURL.Path) if err != nil { return result, fmt.Errorf("hwid component of KDS URL is not a hex string: %q", parsed.simpleURL.Path) } if len(hwid) != abi.ChipIDSize { return result, fmt.Errorf("hwid component of KDS URL has size %d, want %d", len(hwid), abi.ChipIDSize) } result.HWID = hwid result.TCB, err = parseTCBURL(parsed.simpleURL) return result, err } // ParseVLEKCertURL returns the attestation report components represented in the given KDS VLEK // certificate request URL. func ParseVLEKCertURL(kdsurl string) (VLEKCert, error) { result := VLEKCert{} parsed, err := parseBaseProductURL(kdsurl) if err != nil { return result, err } if parsed.function != VlekCertFunction { return result, fmt.Errorf("not a VLEK certificate URL: %s", kdsurl) } result.Product = parsed.productLine // TODO(Issue#114): Remove. result.ProductLine = parsed.productLine if parsed.simpleURL.Path != "cert" { return result, fmt.Errorf("vlek function is %q, want 'cert'", parsed.simpleURL.Path) } result.TCB, err = parseTCBURL(parsed.simpleURL) return result, err } // ProductString returns the KDS product argument to use for the product associated with // an attestation report proto. // // Deprecated: Use ProductLine() func ProductString(product *pb.SevProduct) string { return ProductLine(product) } // ProductLine returns the KDS product argument to use for the product associated with // an attestation report proto. func ProductLine(product *pb.SevProduct) string { if product == nil { product = abi.DefaultSevProduct() } switch product.Name { case pb.SevProduct_SEV_PRODUCT_MILAN: return "Milan" case pb.SevProduct_SEV_PRODUCT_GENOA: return "Genoa" case pb.SevProduct_SEV_PRODUCT_TURIN: return "Turin" default: return "Unknown" } } // ProductLineOfProductName returns the product represented by productNameOrProductLine, i.e., // without the stepping suffix. func ProductLineOfProductName(productNameOrProductLine string) string { product, err := ParseProductLine(productNameOrProductLine) if err != nil { product, err = ParseProductName(productNameOrProductLine, abi.VcekReportSigner) } if err != nil { return "Unknown" } return ProductLine(product) } // DefaultProductString returns the product line of the default SEV product. // // Deprecated: Use DefaultProductLine() func DefaultProductString() string { return DefaultProductLine() } // DefaultProductLine returns the product line of the default SEV product. func DefaultProductLine() string { return ProductLine(abi.DefaultSevProduct()) } // ProductName returns the expected productName extension value for the product associated // with an attestation report proto. func ProductName(product *pb.SevProduct) string { if product == nil { product = abi.DefaultSevProduct() } // Can't produce a product name without a stepping value. if product.MachineStepping == nil { return "UnknownStepping" } stepping := product.MachineStepping.Value if stepping > 15 { return "badstepping" } switch product.Name { case pb.SevProduct_SEV_PRODUCT_MILAN: if int(stepping) >= len(milanSteppingVersions) { return "unmappedMilanStepping" } return fmt.Sprintf("Milan-%s", milanSteppingVersions[stepping]) case pb.SevProduct_SEV_PRODUCT_GENOA: if int(stepping) >= len(genoaSteppingVersions) { return "unmappedGenoaStepping" } return fmt.Sprintf("Genoa-%s", genoaSteppingVersions[stepping]) case pb.SevProduct_SEV_PRODUCT_TURIN: if int(stepping) >= len(turinSteppingVersions) { return "unmappedTurinStepping" } return fmt.Sprintf("Turin-%s", turinSteppingVersions[stepping]) default: return "Unknown" } } // ProductLineFromFms returns the product name used in the KDS endpoint to fetch VCEK certificates. func ProductLineFromFms(fms uint32) string { return ProductLine(abi.SevProductFromCpuid1Eax(fms)) } // ParseProduct returns the SevProductName for a product name without the stepping suffix. // // Deprecated: Use ParseProductLine func ParseProduct(productLine string) (pb.SevProduct_SevProductName, error) { p, err := ParseProductLine(productLine) if err != nil { return pb.SevProduct_SEV_PRODUCT_UNKNOWN, nil } return p.Name, nil } // ParseProductLine returns the SevProductName for a product name without the stepping suffix. func ParseProductLine(productLine string) (*pb.SevProduct, error) { switch productLine { case "Milan": return &pb.SevProduct{Name: pb.SevProduct_SEV_PRODUCT_MILAN}, nil case "Genoa": return &pb.SevProduct{Name: pb.SevProduct_SEV_PRODUCT_GENOA}, nil case "Turin": return &pb.SevProduct{Name: pb.SevProduct_SEV_PRODUCT_TURIN}, nil default: return nil, fmt.Errorf("unknown AMD SEV product: %q", productLine) } } // ParseProductName returns the KDS project input value, and the model, stepping numbers represented // by a given V[CL]EK productName extension value, or an error. func ParseProductName(productName string, key abi.ReportSigner) (*pb.SevProduct, error) { switch key { case abi.VcekReportSigner: product, ok := steppingDecoder[productName] if !ok { return nil, fmt.Errorf("unknown product name (new stepping published?): %q", productName) } return product, nil case abi.VlekReportSigner: // VLEK certificates don't carry the stepping value in productName. return ParseProductLine(productName) } return nil, fmt.Errorf("internal: unhandled reportSigner %v", key) } // CrlLinkByKey returns the CRL distribution point for the given key type's // product. If key is VlekReportSigner, then we use the vlek endpoint. The ASK // and ARK are both on the vcek endpoint. func CrlLinkByKey(productLine string, key abi.ReportSigner) string { return fmt.Sprintf("%s/crl", productBaseURL(key, productLine)) } // CrlLinkByRole returns the CRL distribution point for the given key role's // product. If role is "ASVK", then we use the vlek endpoint. The ASK and ARK // are both on the vcek endpoint. func CrlLinkByRole(productLine, role string) string { key := abi.VcekReportSigner if role == "ASVK" { key = abi.VlekReportSigner } return CrlLinkByKey(productLine, key) } go-sev-guest-0.13.0/kds/kds_test.go000066400000000000000000000214231476044460400170630ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kds import ( "encoding/hex" "fmt" "net/url" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-sev-guest/abi" pb "github.com/google/go-sev-guest/proto/sevsnp" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/wrapperspb" ) func TestProductCertChainURL(t *testing.T) { got := ProductCertChainURL(abi.VcekReportSigner, "Milan") want := "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain" if got != want { t.Errorf("ProductCertChainURL(\"Milan\") = %q, want %q", got, want) } } func TestVCEKCertURL(t *testing.T) { hwid := make([]byte, abi.ChipIDSize) hwid[0] = 0xfe hwid[abi.ChipIDSize-1] = 0xc0 got := VCEKCertURL("Milan", hwid, TCBVersion(0)) want := "https://kdsintf.amd.com/vcek/v1/Milan/fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0?blSPL=0&teeSPL=0&snpSPL=0&ucodeSPL=0" if got != want { t.Errorf("VCEKCertURL(\"Milan\", %v, 0) = %q, want %q", hwid, got, want) } } func TestParseProductBaseURL(t *testing.T) { tcs := []struct { name string url string wantProduct string wantURL *url.URL wantErr string }{ { name: "happy path", url: ProductCertChainURL(abi.VcekReportSigner, "Milan"), wantProduct: "Milan", wantURL: &url.URL{ Scheme: "https", Host: "kdsintf.amd.com", Path: "cert_chain", // The vcek/v1/Milan part is expected to be trimmed. }, }, { name: "bad host", url: "https://fakekds.com/vcek/v1/Milan/cert_chain", wantErr: "unexpected AMD KDS URL host \"fakekds.com\", want \"kdsintf.amd.com\"", }, { name: "bad scheme", url: "http://kdsintf.amd.com/vcek/v1/Milan/cert_chain", wantErr: "unexpected AMD KDS URL scheme \"http\", want \"https\"", }, { name: "bad path", url: "https://kdsintf.amd.com/vcek/v2/Milan/cert_chain", wantErr: "unexpected AMD KDS URL path \"/vcek/v2/Milan/cert_chain\", want prefix \"/vcek/v1/\"", }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { parsed, err := parseBaseProductURL(tc.url) if (err == nil && tc.wantErr != "") || (err != nil && !strings.Contains(err.Error(), tc.wantErr)) { t.Fatalf("parseBaseProductURL(%q) = _, _, %v, want %q", tc.url, err, tc.wantErr) } if err == nil { if diff := cmp.Diff(parsed.simpleURL, tc.wantURL); diff != "" { t.Errorf("parseBaseProductURL(%q) returned unexpected diff (-want +got):\n%s", tc.url, diff) } if parsed.productLine != tc.wantProduct { t.Errorf("parseBaseProductURL(%q) = %q, _, _ want %q", tc.url, parsed.productLine, tc.wantProduct) } } }) } } func TestParseProductCertChainURL(t *testing.T) { tests := []struct { key abi.ReportSigner product string wantKey CertFunction }{ { key: abi.VcekReportSigner, product: "Milan", wantKey: VcekCertFunction, }, { key: abi.VlekReportSigner, product: "Milan", wantKey: VlekCertFunction, }, } for _, tc := range tests { url := ProductCertChainURL(tc.key, tc.product) got, key, err := ParseProductCertChainURL(url) if err != nil { t.Fatalf("ParseProductCertChainURL(%q) = _, _, %v, want nil", tc.product, err) } if got != tc.product || key != tc.wantKey { t.Errorf("ProductCertChainURL(%q) = %q, %v, nil want %q, %v", url, got, key, tc.product, tc.wantKey) } } } func TestParseVCEKCertURL(t *testing.T) { hwid := make([]byte, abi.ChipIDSize) hwidhex := hex.EncodeToString(hwid) tcs := []struct { name string url string want VCEKCert wantErr string }{ { name: "happy path", url: VCEKCertURL("Milan", hwid, TCBVersion(0)), want: func() VCEKCert { c := VCEKCertProduct("Milan") c.HWID = hwid c.TCB = 0 return c }(), }, { name: "bad query format", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?ha;ha", hwidhex), wantErr: "invalid AMD KDS URL query \"ha;ha\": invalid semicolon separator in query", }, { name: "bad query key", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?fakespl=4", hwidhex), wantErr: "unexpected KDS TCB version URL argument \"fakespl\"", }, { name: "bad query argument numerical", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?blSPL=-4", hwidhex), wantErr: "invalid KDS TCB version URL argument value \"-4\", want a value 0-255", }, { name: "bad query argument numerical", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?blSPL=alpha", hwidhex), wantErr: "invalid KDS TCB version URL argument value \"alpha\", want a value 0-255", }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { got, err := ParseVCEKCertURL(tc.url) if (err == nil && tc.wantErr != "") || (err != nil && !strings.Contains(err.Error(), tc.wantErr)) { t.Fatalf("ParseVCEKCertURL(%q) = _, %v, want %q", tc.url, err, tc.wantErr) } if err == nil { if diff := cmp.Diff(got, tc.want); diff != "" { t.Errorf("ParseVCEKCertURL(%q) returned unexpected diff (-want +got):\n%s", tc.url, diff) } } }) } } func TestProductName(t *testing.T) { tcs := []struct { name string input *pb.SevProduct want string }{ { name: "nil", want: "Milan-B1", }, { name: "unknown", input: &pb.SevProduct{ MachineStepping: &wrapperspb.UInt32Value{Value: 0x1A}, }, want: "badstepping", }, { name: "Milan-B0", input: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_MILAN, }, want: "UnknownStepping", }, { name: "Milan-B0", input: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 0}, }, want: "Milan-B0", }, { name: "Genoa-FF", input: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: &wrapperspb.UInt32Value{Value: 0xff}, }, want: "badstepping", }, { name: "unknown milan stepping", input: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 15}, }, want: "unmappedMilanStepping", }, { name: "unknown genoa stepping", input: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: &wrapperspb.UInt32Value{Value: 15}, }, want: "unmappedGenoaStepping", }, { name: "unknown", input: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_UNKNOWN, MachineStepping: &wrapperspb.UInt32Value{Value: 15}, }, want: "Unknown", }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { if got := ProductName(tc.input); got != tc.want { t.Errorf("ProductName(%v) = %q, want %q", tc.input, got, tc.want) } }) } } func TestParseProductName(t *testing.T) { tcs := []struct { name string input string key abi.ReportSigner want *pb.SevProduct wantErr string }{ { name: "empty", wantErr: "unknown product name", }, { name: "Too big", input: "Milan-100", wantErr: "unknown product name", }, { name: "happy path Genoa", input: "Genoa-B1", want: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_GENOA, MachineStepping: &wrapperspb.UInt32Value{Value: 1}, }, }, { name: "bad revision Milan", input: "Milan-A1", wantErr: "unknown product name", }, { name: "vlek products have no stepping", input: "Genoa", key: abi.VlekReportSigner, want: &pb.SevProduct{ Name: pb.SevProduct_SEV_PRODUCT_GENOA, }, }, { name: "Unhandled report signer", input: "ignored", key: abi.NoneReportSigner, wantErr: "internal: unhandled reportSigner", }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { got, err := ParseProductName(tc.input, tc.key) if (err == nil && tc.wantErr != "") || (err != nil && (tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr))) { t.Fatalf("ParseProductName(%v) errored unexpectedly: %v, want %q", tc.input, err, tc.wantErr) } if tc.wantErr == "" { if diff := cmp.Diff(got, tc.want, protocmp.Transform()); diff != "" { t.Fatalf("ParseProductName(%v) = %v, want %v\nDiff: %s", tc.input, got, tc.want, diff) } } }) } } go-sev-guest-0.13.0/proto/000077500000000000000000000000001476044460400152745ustar00rootroot00000000000000go-sev-guest-0.13.0/proto/check.proto000066400000000000000000000073251476044460400174450ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; // Package check represents an attestation validation policy. package check; import "google/protobuf/wrappers.proto"; import "sevsnp.proto"; option go_package = "github.com/google/go-sev-guest/proto/check"; // Policy is a representation of an attestation report validation policy. // Each field corresponds to a field on validate.Options. This format // is useful for providing programmatic inputs to the `check` CLI tool. message Policy { uint32 minimum_guest_svn = 1; // The component-wise maximum permissible guest policy, except // API version values, and SingleSocket are the minimum permissible. uint64 policy = 2; bytes family_id = 3; // Should be 16 bytes long bytes image_id = 4; // Should be 16 bytes long google.protobuf.UInt32Value vmpl = 5; uint64 minimum_tcb = 6; uint64 minimum_launch_tcb = 7; google.protobuf.UInt64Value platform_info = 8; bool require_author_key = 9; bytes report_data = 10; // Should be 64 bytes long bytes measurement = 11; // Should be 48 bytes long bytes host_data = 12; // Should be 32 bytes long bytes report_id = 13; // Should be 32 bytes long bytes report_id_ma = 14; // Should be 32 bytes long bytes chip_id = 15; // Should be 64 bytes long uint32 minimum_build = 16; string minimum_version = 17; // Should be "maj.min", both should be 0-255. bool permit_provisional_firmware = 18; bool require_id_block = 19; repeated bytes trusted_author_keys = 20; repeated bytes trusted_author_key_hashes = 21; repeated bytes trusted_id_keys = 22; repeated bytes trusted_id_key_hashes = 23; // The expected product that generated the attestation report. Stepping optional. sevsnp.SevProduct product = 24; } // RootOfTrust represents configuration for which hardware root of trust // certificates to use for verifying attestation report signatures. message RootOfTrust { // The expected AMD product the attestation was collected from. Default // "Milan". string product = 1 [deprecated = true]; // Paths to CA bundles for the AMD product. // Must be in PEM format, AS[V]K, then ARK certificates. // This is for verifing a report's signature, as opposed to validating trust // in the report's ID key or author key. // If empty, uses the verification library's embedded certificates from AMD. repeated string cabundle_paths = 2; // PEM format CA bundles for the AMD product. Combined with contents of cabundle_paths. repeated string cabundles = 3; // If true, download and check the CRL for revoked certificates. bool check_crl = 4; // If true, then check is not permitted to download necessary files for verification. bool disallow_network = 5; // The expected AMD product line the attestation was collected from. Default "Milan". string product_line = 6; } // Config is the overall message input for the check tool. This provides all // the flags that configure the tool, including the validation policy. message Config { // Configures which hardware keys to trust. Default uses library-embedded // certificates. RootOfTrust root_of_trust = 1; // The report validation policy. Policy policy = 2; } go-sev-guest-0.13.0/proto/check/000077500000000000000000000000001476044460400163515ustar00rootroot00000000000000go-sev-guest-0.13.0/proto/check/check.pb.go000066400000000000000000000616051476044460400203650ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.31.0 // protoc v5.27.2 // source: check.proto // Package check represents an attestation validation policy. package check import ( sevsnp "github.com/google/go-sev-guest/proto/sevsnp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // Policy is a representation of an attestation report validation policy. // Each field corresponds to a field on validate.Options. This format // is useful for providing programmatic inputs to the `check` CLI tool. type Policy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields MinimumGuestSvn uint32 `protobuf:"varint,1,opt,name=minimum_guest_svn,json=minimumGuestSvn,proto3" json:"minimum_guest_svn,omitempty"` // The component-wise maximum permissible guest policy, except // API version values, and SingleSocket are the minimum permissible. Policy uint64 `protobuf:"varint,2,opt,name=policy,proto3" json:"policy,omitempty"` FamilyId []byte `protobuf:"bytes,3,opt,name=family_id,json=familyId,proto3" json:"family_id,omitempty"` // Should be 16 bytes long ImageId []byte `protobuf:"bytes,4,opt,name=image_id,json=imageId,proto3" json:"image_id,omitempty"` // Should be 16 bytes long Vmpl *wrapperspb.UInt32Value `protobuf:"bytes,5,opt,name=vmpl,proto3" json:"vmpl,omitempty"` MinimumTcb uint64 `protobuf:"varint,6,opt,name=minimum_tcb,json=minimumTcb,proto3" json:"minimum_tcb,omitempty"` MinimumLaunchTcb uint64 `protobuf:"varint,7,opt,name=minimum_launch_tcb,json=minimumLaunchTcb,proto3" json:"minimum_launch_tcb,omitempty"` PlatformInfo *wrapperspb.UInt64Value `protobuf:"bytes,8,opt,name=platform_info,json=platformInfo,proto3" json:"platform_info,omitempty"` RequireAuthorKey bool `protobuf:"varint,9,opt,name=require_author_key,json=requireAuthorKey,proto3" json:"require_author_key,omitempty"` ReportData []byte `protobuf:"bytes,10,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be 64 bytes long Measurement []byte `protobuf:"bytes,11,opt,name=measurement,proto3" json:"measurement,omitempty"` // Should be 48 bytes long HostData []byte `protobuf:"bytes,12,opt,name=host_data,json=hostData,proto3" json:"host_data,omitempty"` // Should be 32 bytes long ReportId []byte `protobuf:"bytes,13,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` // Should be 32 bytes long ReportIdMa []byte `protobuf:"bytes,14,opt,name=report_id_ma,json=reportIdMa,proto3" json:"report_id_ma,omitempty"` // Should be 32 bytes long ChipId []byte `protobuf:"bytes,15,opt,name=chip_id,json=chipId,proto3" json:"chip_id,omitempty"` // Should be 64 bytes long MinimumBuild uint32 `protobuf:"varint,16,opt,name=minimum_build,json=minimumBuild,proto3" json:"minimum_build,omitempty"` MinimumVersion string `protobuf:"bytes,17,opt,name=minimum_version,json=minimumVersion,proto3" json:"minimum_version,omitempty"` // Should be "maj.min", both should be 0-255. PermitProvisionalFirmware bool `protobuf:"varint,18,opt,name=permit_provisional_firmware,json=permitProvisionalFirmware,proto3" json:"permit_provisional_firmware,omitempty"` RequireIdBlock bool `protobuf:"varint,19,opt,name=require_id_block,json=requireIdBlock,proto3" json:"require_id_block,omitempty"` TrustedAuthorKeys [][]byte `protobuf:"bytes,20,rep,name=trusted_author_keys,json=trustedAuthorKeys,proto3" json:"trusted_author_keys,omitempty"` TrustedAuthorKeyHashes [][]byte `protobuf:"bytes,21,rep,name=trusted_author_key_hashes,json=trustedAuthorKeyHashes,proto3" json:"trusted_author_key_hashes,omitempty"` TrustedIdKeys [][]byte `protobuf:"bytes,22,rep,name=trusted_id_keys,json=trustedIdKeys,proto3" json:"trusted_id_keys,omitempty"` TrustedIdKeyHashes [][]byte `protobuf:"bytes,23,rep,name=trusted_id_key_hashes,json=trustedIdKeyHashes,proto3" json:"trusted_id_key_hashes,omitempty"` // The expected product that generated the attestation report. Stepping optional. Product *sevsnp.SevProduct `protobuf:"bytes,24,opt,name=product,proto3" json:"product,omitempty"` } func (x *Policy) Reset() { *x = Policy{} if protoimpl.UnsafeEnabled { mi := &file_check_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Policy) String() string { return protoimpl.X.MessageStringOf(x) } func (*Policy) ProtoMessage() {} func (x *Policy) ProtoReflect() protoreflect.Message { mi := &file_check_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Policy.ProtoReflect.Descriptor instead. func (*Policy) Descriptor() ([]byte, []int) { return file_check_proto_rawDescGZIP(), []int{0} } func (x *Policy) GetMinimumGuestSvn() uint32 { if x != nil { return x.MinimumGuestSvn } return 0 } func (x *Policy) GetPolicy() uint64 { if x != nil { return x.Policy } return 0 } func (x *Policy) GetFamilyId() []byte { if x != nil { return x.FamilyId } return nil } func (x *Policy) GetImageId() []byte { if x != nil { return x.ImageId } return nil } func (x *Policy) GetVmpl() *wrapperspb.UInt32Value { if x != nil { return x.Vmpl } return nil } func (x *Policy) GetMinimumTcb() uint64 { if x != nil { return x.MinimumTcb } return 0 } func (x *Policy) GetMinimumLaunchTcb() uint64 { if x != nil { return x.MinimumLaunchTcb } return 0 } func (x *Policy) GetPlatformInfo() *wrapperspb.UInt64Value { if x != nil { return x.PlatformInfo } return nil } func (x *Policy) GetRequireAuthorKey() bool { if x != nil { return x.RequireAuthorKey } return false } func (x *Policy) GetReportData() []byte { if x != nil { return x.ReportData } return nil } func (x *Policy) GetMeasurement() []byte { if x != nil { return x.Measurement } return nil } func (x *Policy) GetHostData() []byte { if x != nil { return x.HostData } return nil } func (x *Policy) GetReportId() []byte { if x != nil { return x.ReportId } return nil } func (x *Policy) GetReportIdMa() []byte { if x != nil { return x.ReportIdMa } return nil } func (x *Policy) GetChipId() []byte { if x != nil { return x.ChipId } return nil } func (x *Policy) GetMinimumBuild() uint32 { if x != nil { return x.MinimumBuild } return 0 } func (x *Policy) GetMinimumVersion() string { if x != nil { return x.MinimumVersion } return "" } func (x *Policy) GetPermitProvisionalFirmware() bool { if x != nil { return x.PermitProvisionalFirmware } return false } func (x *Policy) GetRequireIdBlock() bool { if x != nil { return x.RequireIdBlock } return false } func (x *Policy) GetTrustedAuthorKeys() [][]byte { if x != nil { return x.TrustedAuthorKeys } return nil } func (x *Policy) GetTrustedAuthorKeyHashes() [][]byte { if x != nil { return x.TrustedAuthorKeyHashes } return nil } func (x *Policy) GetTrustedIdKeys() [][]byte { if x != nil { return x.TrustedIdKeys } return nil } func (x *Policy) GetTrustedIdKeyHashes() [][]byte { if x != nil { return x.TrustedIdKeyHashes } return nil } func (x *Policy) GetProduct() *sevsnp.SevProduct { if x != nil { return x.Product } return nil } // RootOfTrust represents configuration for which hardware root of trust // certificates to use for verifying attestation report signatures. type RootOfTrust struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The expected AMD product the attestation was collected from. Default // "Milan". // // Deprecated: Marked as deprecated in check.proto. Product string `protobuf:"bytes,1,opt,name=product,proto3" json:"product,omitempty"` // Paths to CA bundles for the AMD product. // Must be in PEM format, AS[V]K, then ARK certificates. // This is for verifing a report's signature, as opposed to validating trust // in the report's ID key or author key. // If empty, uses the verification library's embedded certificates from AMD. CabundlePaths []string `protobuf:"bytes,2,rep,name=cabundle_paths,json=cabundlePaths,proto3" json:"cabundle_paths,omitempty"` // PEM format CA bundles for the AMD product. Combined with contents of cabundle_paths. Cabundles []string `protobuf:"bytes,3,rep,name=cabundles,proto3" json:"cabundles,omitempty"` // If true, download and check the CRL for revoked certificates. CheckCrl bool `protobuf:"varint,4,opt,name=check_crl,json=checkCrl,proto3" json:"check_crl,omitempty"` // If true, then check is not permitted to download necessary files for verification. DisallowNetwork bool `protobuf:"varint,5,opt,name=disallow_network,json=disallowNetwork,proto3" json:"disallow_network,omitempty"` // The expected AMD product line the attestation was collected from. Default "Milan". ProductLine string `protobuf:"bytes,6,opt,name=product_line,json=productLine,proto3" json:"product_line,omitempty"` } func (x *RootOfTrust) Reset() { *x = RootOfTrust{} if protoimpl.UnsafeEnabled { mi := &file_check_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RootOfTrust) String() string { return protoimpl.X.MessageStringOf(x) } func (*RootOfTrust) ProtoMessage() {} func (x *RootOfTrust) ProtoReflect() protoreflect.Message { mi := &file_check_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RootOfTrust.ProtoReflect.Descriptor instead. func (*RootOfTrust) Descriptor() ([]byte, []int) { return file_check_proto_rawDescGZIP(), []int{1} } // Deprecated: Marked as deprecated in check.proto. func (x *RootOfTrust) GetProduct() string { if x != nil { return x.Product } return "" } func (x *RootOfTrust) GetCabundlePaths() []string { if x != nil { return x.CabundlePaths } return nil } func (x *RootOfTrust) GetCabundles() []string { if x != nil { return x.Cabundles } return nil } func (x *RootOfTrust) GetCheckCrl() bool { if x != nil { return x.CheckCrl } return false } func (x *RootOfTrust) GetDisallowNetwork() bool { if x != nil { return x.DisallowNetwork } return false } func (x *RootOfTrust) GetProductLine() string { if x != nil { return x.ProductLine } return "" } // Config is the overall message input for the check tool. This provides all // the flags that configure the tool, including the validation policy. type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Configures which hardware keys to trust. Default uses library-embedded // certificates. RootOfTrust *RootOfTrust `protobuf:"bytes,1,opt,name=root_of_trust,json=rootOfTrust,proto3" json:"root_of_trust,omitempty"` // The report validation policy. Policy *Policy `protobuf:"bytes,2,opt,name=policy,proto3" json:"policy,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_check_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_check_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_check_proto_rawDescGZIP(), []int{2} } func (x *Config) GetRootOfTrust() *RootOfTrust { if x != nil { return x.RootOfTrust } return nil } func (x *Config) GetPolicy() *Policy { if x != nil { return x.Policy } return nil } var File_check_proto protoreflect.FileDescriptor var file_check_proto_rawDesc = []byte{ 0x0a, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x07, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x47, 0x75, 0x65, 0x73, 0x74, 0x53, 0x76, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x04, 0x76, 0x6d, 0x70, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x76, 0x6d, 0x70, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x54, 0x63, 0x62, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x54, 0x63, 0x62, 0x12, 0x41, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x6d, 0x61, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x4d, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x1b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x49, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x11, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x16, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x49, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x17, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x49, 0x64, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0xdb, 0x01, 0x0a, 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x4f, 0x66, 0x54, 0x72, 0x75, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x61, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x61, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x63, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x72, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0x67, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x4f, 0x66, 0x54, 0x72, 0x75, 0x73, 0x74, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x4f, 0x66, 0x54, 0x72, 0x75, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x65, 0x76, 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_check_proto_rawDescOnce sync.Once file_check_proto_rawDescData = file_check_proto_rawDesc ) func file_check_proto_rawDescGZIP() []byte { file_check_proto_rawDescOnce.Do(func() { file_check_proto_rawDescData = protoimpl.X.CompressGZIP(file_check_proto_rawDescData) }) return file_check_proto_rawDescData } var file_check_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_check_proto_goTypes = []interface{}{ (*Policy)(nil), // 0: check.Policy (*RootOfTrust)(nil), // 1: check.RootOfTrust (*Config)(nil), // 2: check.Config (*wrapperspb.UInt32Value)(nil), // 3: google.protobuf.UInt32Value (*wrapperspb.UInt64Value)(nil), // 4: google.protobuf.UInt64Value (*sevsnp.SevProduct)(nil), // 5: sevsnp.SevProduct } var file_check_proto_depIdxs = []int32{ 3, // 0: check.Policy.vmpl:type_name -> google.protobuf.UInt32Value 4, // 1: check.Policy.platform_info:type_name -> google.protobuf.UInt64Value 5, // 2: check.Policy.product:type_name -> sevsnp.SevProduct 1, // 3: check.Config.root_of_trust:type_name -> check.RootOfTrust 0, // 4: check.Config.policy:type_name -> check.Policy 5, // [5:5] is the sub-list for method output_type 5, // [5:5] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name } func init() { file_check_proto_init() } func file_check_proto_init() { if File_check_proto != nil { return } if !protoimpl.UnsafeEnabled { file_check_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Policy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_check_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RootOfTrust); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_check_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_check_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_check_proto_goTypes, DependencyIndexes: file_check_proto_depIdxs, MessageInfos: file_check_proto_msgTypes, }.Build() File_check_proto = out.File file_check_proto_rawDesc = nil file_check_proto_goTypes = nil file_check_proto_depIdxs = nil } go-sev-guest-0.13.0/proto/check/doc.go000066400000000000000000000012471476044460400174510ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package check defines the message type for the check CLI tool's options. package check go-sev-guest-0.13.0/proto/doc.go000066400000000000000000000042411476044460400163710ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package proto contains protocol buffers that are exchanged between the client // and server, as well as convenience configuration definitions for tools. // // # Generating Protocol Buffer Code // // Anytime the Protocol Buffer definitions change, the generated Go code must be // regenerated. This can be done with "go generate". Just run: // // go generate ./... // // Upstream documentation: // https://developers.google.com/protocol-buffers/docs/reference/go-generated // // # Code Generation Dependencies // // To generate the Go code, your system must have "protoc" installed. See: // https://github.com/protocolbuffers/protobuf#protocol-compiler-installation // // The "protoc-gen-go" tool must also be installed. To install it, run: // // go install google.golang.org/protobuf/cmd/protoc-gen-go // // If you see a 'protoc-gen-go: program not found or is not executable' error // for the 'go generate' command, run the following: // // echo 'export PATH=$PATH:$GOPATH/bin' >> $HOME/.bashrc // source $HOME/.bashrc // // If you see 'google/protobuf/wrappers.proto not found', then you need to // similarly set your PROTOC_INSTALL_DIR environment variable to the protoc // installation directory which should have the "well-known types" in the // include subdirectory. package proto //go:generate protoc -I$PROTOC_INSTALL_DIR/include -I=. --go_out=. --go_opt=module=github.com/google/go-sev-guest/proto check.proto //go:generate protoc --go_out=. --go_opt=module=github.com/google/go-sev-guest/proto fakekds.proto //go:generate protoc --go_out=. --go_opt=module=github.com/google/go-sev-guest/proto sevsnp.proto go-sev-guest-0.13.0/proto/fakekds.proto000066400000000000000000000021651476044460400177750ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package fakekds; option go_package = "github.com/google/go-sev-guest/proto/fakekds"; // Certificates represents all known certificates for machines at particular // TCB values. This is useful to represent a test machine cluster's VCEK // certificates that haven't been provisioned with the /dev/sev device. message Certificates { message ChipTCBCerts { bytes chip_id = 1; // Should be 64 bytes map tcb_certs = 2; string hostname = 3; uint32 fms = 4; } repeated ChipTCBCerts chip_certs = 1; } go-sev-guest-0.13.0/proto/fakekds/000077500000000000000000000000001476044460400167045ustar00rootroot00000000000000go-sev-guest-0.13.0/proto/fakekds/doc.go000066400000000000000000000015361476044460400200050ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package fakekds defines the message types for representing a local cache of // KDS certificates for a small set of test machines. This is useful for creating // a reliable testing environment that does not depend on unbroken service from // the AMD KDS. package fakekds go-sev-guest-0.13.0/proto/fakekds/fakekds.pb.go000066400000000000000000000225131476044460400212460ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.31.0 // protoc v5.27.2 // source: fakekds.proto package fakekds import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // Certificates represents all known certificates for machines at particular // TCB values. This is useful to represent a test machine cluster's VCEK // certificates that haven't been provisioned with the /dev/sev device. type Certificates struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ChipCerts []*Certificates_ChipTCBCerts `protobuf:"bytes,1,rep,name=chip_certs,json=chipCerts,proto3" json:"chip_certs,omitempty"` } func (x *Certificates) Reset() { *x = Certificates{} if protoimpl.UnsafeEnabled { mi := &file_fakekds_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Certificates) String() string { return protoimpl.X.MessageStringOf(x) } func (*Certificates) ProtoMessage() {} func (x *Certificates) ProtoReflect() protoreflect.Message { mi := &file_fakekds_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Certificates.ProtoReflect.Descriptor instead. func (*Certificates) Descriptor() ([]byte, []int) { return file_fakekds_proto_rawDescGZIP(), []int{0} } func (x *Certificates) GetChipCerts() []*Certificates_ChipTCBCerts { if x != nil { return x.ChipCerts } return nil } type Certificates_ChipTCBCerts struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ChipId []byte `protobuf:"bytes,1,opt,name=chip_id,json=chipId,proto3" json:"chip_id,omitempty"` // Should be 64 bytes TcbCerts map[uint64][]byte `protobuf:"bytes,2,rep,name=tcb_certs,json=tcbCerts,proto3" json:"tcb_certs,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` Fms uint32 `protobuf:"varint,4,opt,name=fms,proto3" json:"fms,omitempty"` } func (x *Certificates_ChipTCBCerts) Reset() { *x = Certificates_ChipTCBCerts{} if protoimpl.UnsafeEnabled { mi := &file_fakekds_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Certificates_ChipTCBCerts) String() string { return protoimpl.X.MessageStringOf(x) } func (*Certificates_ChipTCBCerts) ProtoMessage() {} func (x *Certificates_ChipTCBCerts) ProtoReflect() protoreflect.Message { mi := &file_fakekds_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Certificates_ChipTCBCerts.ProtoReflect.Descriptor instead. func (*Certificates_ChipTCBCerts) Descriptor() ([]byte, []int) { return file_fakekds_proto_rawDescGZIP(), []int{0, 0} } func (x *Certificates_ChipTCBCerts) GetChipId() []byte { if x != nil { return x.ChipId } return nil } func (x *Certificates_ChipTCBCerts) GetTcbCerts() map[uint64][]byte { if x != nil { return x.TcbCerts } return nil } func (x *Certificates_ChipTCBCerts) GetHostname() string { if x != nil { return x.Hostname } return "" } func (x *Certificates_ChipTCBCerts) GetFms() uint32 { if x != nil { return x.Fms } return 0 } var File_fakekds_proto protoreflect.FileDescriptor var file_fakekds_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x66, 0x61, 0x6b, 0x65, 0x6b, 0x64, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x66, 0x61, 0x6b, 0x65, 0x6b, 0x64, 0x73, 0x22, 0xb5, 0x02, 0x0a, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x6b, 0x64, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x68, 0x69, 0x70, 0x54, 0x43, 0x42, 0x43, 0x65, 0x72, 0x74, 0x73, 0x52, 0x09, 0x63, 0x68, 0x69, 0x70, 0x43, 0x65, 0x72, 0x74, 0x73, 0x1a, 0xe1, 0x01, 0x0a, 0x0c, 0x43, 0x68, 0x69, 0x70, 0x54, 0x43, 0x42, 0x43, 0x65, 0x72, 0x74, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x09, 0x74, 0x63, 0x62, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x6b, 0x64, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x68, 0x69, 0x70, 0x54, 0x43, 0x42, 0x43, 0x65, 0x72, 0x74, 0x73, 0x2e, 0x54, 0x63, 0x62, 0x43, 0x65, 0x72, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x74, 0x63, 0x62, 0x43, 0x65, 0x72, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x66, 0x6d, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x54, 0x63, 0x62, 0x43, 0x65, 0x72, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x65, 0x76, 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x6b, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_fakekds_proto_rawDescOnce sync.Once file_fakekds_proto_rawDescData = file_fakekds_proto_rawDesc ) func file_fakekds_proto_rawDescGZIP() []byte { file_fakekds_proto_rawDescOnce.Do(func() { file_fakekds_proto_rawDescData = protoimpl.X.CompressGZIP(file_fakekds_proto_rawDescData) }) return file_fakekds_proto_rawDescData } var file_fakekds_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_fakekds_proto_goTypes = []interface{}{ (*Certificates)(nil), // 0: fakekds.Certificates (*Certificates_ChipTCBCerts)(nil), // 1: fakekds.Certificates.ChipTCBCerts nil, // 2: fakekds.Certificates.ChipTCBCerts.TcbCertsEntry } var file_fakekds_proto_depIdxs = []int32{ 1, // 0: fakekds.Certificates.chip_certs:type_name -> fakekds.Certificates.ChipTCBCerts 2, // 1: fakekds.Certificates.ChipTCBCerts.tcb_certs:type_name -> fakekds.Certificates.ChipTCBCerts.TcbCertsEntry 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_fakekds_proto_init() } func file_fakekds_proto_init() { if File_fakekds_proto != nil { return } if !protoimpl.UnsafeEnabled { file_fakekds_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Certificates); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fakekds_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Certificates_ChipTCBCerts); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_fakekds_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_fakekds_proto_goTypes, DependencyIndexes: file_fakekds_proto_depIdxs, MessageInfos: file_fakekds_proto_msgTypes, }.Build() File_fakekds_proto = out.File file_fakekds_proto_rawDesc = nil file_fakekds_proto_goTypes = nil file_fakekds_proto_depIdxs = nil } go-sev-guest-0.13.0/proto/sevsnp.proto000066400000000000000000000072761476044460400177130ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; import "google/protobuf/wrappers.proto"; // Package sevsnp represents an SEV-SNP attestation report and its certificate // chain. package sevsnp; option go_package = "github.com/google/go-sev-guest/proto/sevsnp"; // Report represents an SEV-SNP ATTESTATION_REPORT, specified in SEV SNP API // documentation https://www.amd.com/system/files/TechDocs/56860.pdf message Report { uint32 version = 1; // Should be 2 for revision 1.55, and 3 for revision 1.56 uint32 guest_svn = 2; uint64 policy = 3; bytes family_id = 4; // Should be 16 bytes long bytes image_id = 5; // Should be 16 bytes long uint32 vmpl = 6; uint32 signature_algo = 7; uint64 current_tcb = 8; uint64 platform_info = 9; uint32 signer_info = 10; // AuthorKeyEn, MaskChipKey, SigningKey bytes report_data = 11; // Should be 64 bytes long bytes measurement = 12; // Should be 48 bytes long bytes host_data = 13; // Should be 32 bytes long bytes id_key_digest = 14; // Should be 48 bytes long bytes author_key_digest = 15; // Should be 48 bytes long bytes report_id = 16; // Should be 32 bytes long bytes report_id_ma = 17; // Should be 32 bytes long uint64 reported_tcb = 18; bytes chip_id = 19; // Should be 64 bytes long uint64 committed_tcb = 20; // Each build, minor, major triple should be packed together in a uint32 // packed together at 7:0, 15:8, 23:16 respectively uint32 current_build = 21; uint32 current_minor = 22; uint32 current_major = 23; uint32 committed_build = 24; uint32 committed_minor = 25; uint32 committed_major = 26; uint64 launch_tcb = 27; bytes signature = 28; // Should be 512 bytes long uint32 cpuid1eax_fms = 29; // The cpuid(1).eax & 0x0fff0fff representation of family/model/stepping } message CertificateChain { // The versioned chip endorsement key's certificate for the // key that signed this report. bytes vcek_cert = 1; // The versioned loaded endorsement key's certificate for the // key that signed this report. bytes vlek_cert = 6; // The AMD SEV or AMD SEV-VLEK certificate that signed the V?EK cert. bytes ask_cert = 2; // The AMD Root key certificate (signs the ASK cert). bytes ark_cert = 3; // A certificate the host may inject to endorse the measurement of the // firmware. bytes firmware_cert = 4 [deprecated = true]; // Non-standard certificates the host may inject. map extras = 7; } // The CPUID[EAX=1] version information includes product info as described in // the AMD KDS specification. The product name, model, and stepping values are // important for determining the required parameters to KDS when requesting the // endorsement key's certificate. message SevProduct { enum SevProductName { SEV_PRODUCT_UNKNOWN = 0; SEV_PRODUCT_MILAN = 1; SEV_PRODUCT_GENOA = 2; SEV_PRODUCT_TURIN = 3; } SevProductName name = 1; uint32 stepping = 2 [deprecated = true]; // Must be a 4-bit number google.protobuf.UInt32Value machine_stepping = 3; } message Attestation { Report report = 1; CertificateChain certificate_chain = 2; SevProduct product = 3; } go-sev-guest-0.13.0/proto/sevsnp/000077500000000000000000000000001476044460400166125ustar00rootroot00000000000000go-sev-guest-0.13.0/proto/sevsnp/doc.go000066400000000000000000000012621476044460400177070ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package sevsnp implements a protocol buffer for representing SEV-SNP attestations. package sevsnp go-sev-guest-0.13.0/proto/sevsnp/sevsnp.pb.go000066400000000000000000000766761476044460400211050ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.31.0 // protoc v5.27.2 // source: sevsnp.proto // Package sevsnp represents an SEV-SNP attestation report and its certificate // chain. package sevsnp import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type SevProduct_SevProductName int32 const ( SevProduct_SEV_PRODUCT_UNKNOWN SevProduct_SevProductName = 0 SevProduct_SEV_PRODUCT_MILAN SevProduct_SevProductName = 1 SevProduct_SEV_PRODUCT_GENOA SevProduct_SevProductName = 2 SevProduct_SEV_PRODUCT_TURIN SevProduct_SevProductName = 3 ) // Enum value maps for SevProduct_SevProductName. var ( SevProduct_SevProductName_name = map[int32]string{ 0: "SEV_PRODUCT_UNKNOWN", 1: "SEV_PRODUCT_MILAN", 2: "SEV_PRODUCT_GENOA", 3: "SEV_PRODUCT_TURIN", } SevProduct_SevProductName_value = map[string]int32{ "SEV_PRODUCT_UNKNOWN": 0, "SEV_PRODUCT_MILAN": 1, "SEV_PRODUCT_GENOA": 2, "SEV_PRODUCT_TURIN": 3, } ) func (x SevProduct_SevProductName) Enum() *SevProduct_SevProductName { p := new(SevProduct_SevProductName) *p = x return p } func (x SevProduct_SevProductName) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SevProduct_SevProductName) Descriptor() protoreflect.EnumDescriptor { return file_sevsnp_proto_enumTypes[0].Descriptor() } func (SevProduct_SevProductName) Type() protoreflect.EnumType { return &file_sevsnp_proto_enumTypes[0] } func (x SevProduct_SevProductName) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SevProduct_SevProductName.Descriptor instead. func (SevProduct_SevProductName) EnumDescriptor() ([]byte, []int) { return file_sevsnp_proto_rawDescGZIP(), []int{2, 0} } // Report represents an SEV-SNP ATTESTATION_REPORT, specified in SEV SNP API // // documentation https://www.amd.com/system/files/TechDocs/56860.pdf type Report struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` // Should be 2 for revision 1.55, and 3 for revision 1.56 GuestSvn uint32 `protobuf:"varint,2,opt,name=guest_svn,json=guestSvn,proto3" json:"guest_svn,omitempty"` Policy uint64 `protobuf:"varint,3,opt,name=policy,proto3" json:"policy,omitempty"` FamilyId []byte `protobuf:"bytes,4,opt,name=family_id,json=familyId,proto3" json:"family_id,omitempty"` // Should be 16 bytes long ImageId []byte `protobuf:"bytes,5,opt,name=image_id,json=imageId,proto3" json:"image_id,omitempty"` // Should be 16 bytes long Vmpl uint32 `protobuf:"varint,6,opt,name=vmpl,proto3" json:"vmpl,omitempty"` SignatureAlgo uint32 `protobuf:"varint,7,opt,name=signature_algo,json=signatureAlgo,proto3" json:"signature_algo,omitempty"` CurrentTcb uint64 `protobuf:"varint,8,opt,name=current_tcb,json=currentTcb,proto3" json:"current_tcb,omitempty"` PlatformInfo uint64 `protobuf:"varint,9,opt,name=platform_info,json=platformInfo,proto3" json:"platform_info,omitempty"` SignerInfo uint32 `protobuf:"varint,10,opt,name=signer_info,json=signerInfo,proto3" json:"signer_info,omitempty"` // AuthorKeyEn, MaskChipKey, SigningKey ReportData []byte `protobuf:"bytes,11,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be 64 bytes long Measurement []byte `protobuf:"bytes,12,opt,name=measurement,proto3" json:"measurement,omitempty"` // Should be 48 bytes long HostData []byte `protobuf:"bytes,13,opt,name=host_data,json=hostData,proto3" json:"host_data,omitempty"` // Should be 32 bytes long IdKeyDigest []byte `protobuf:"bytes,14,opt,name=id_key_digest,json=idKeyDigest,proto3" json:"id_key_digest,omitempty"` // Should be 48 bytes long AuthorKeyDigest []byte `protobuf:"bytes,15,opt,name=author_key_digest,json=authorKeyDigest,proto3" json:"author_key_digest,omitempty"` // Should be 48 bytes long ReportId []byte `protobuf:"bytes,16,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` // Should be 32 bytes long ReportIdMa []byte `protobuf:"bytes,17,opt,name=report_id_ma,json=reportIdMa,proto3" json:"report_id_ma,omitempty"` // Should be 32 bytes long ReportedTcb uint64 `protobuf:"varint,18,opt,name=reported_tcb,json=reportedTcb,proto3" json:"reported_tcb,omitempty"` ChipId []byte `protobuf:"bytes,19,opt,name=chip_id,json=chipId,proto3" json:"chip_id,omitempty"` // Should be 64 bytes long CommittedTcb uint64 `protobuf:"varint,20,opt,name=committed_tcb,json=committedTcb,proto3" json:"committed_tcb,omitempty"` // Each build, minor, major triple should be packed together in a uint32 // packed together at 7:0, 15:8, 23:16 respectively CurrentBuild uint32 `protobuf:"varint,21,opt,name=current_build,json=currentBuild,proto3" json:"current_build,omitempty"` CurrentMinor uint32 `protobuf:"varint,22,opt,name=current_minor,json=currentMinor,proto3" json:"current_minor,omitempty"` CurrentMajor uint32 `protobuf:"varint,23,opt,name=current_major,json=currentMajor,proto3" json:"current_major,omitempty"` CommittedBuild uint32 `protobuf:"varint,24,opt,name=committed_build,json=committedBuild,proto3" json:"committed_build,omitempty"` CommittedMinor uint32 `protobuf:"varint,25,opt,name=committed_minor,json=committedMinor,proto3" json:"committed_minor,omitempty"` CommittedMajor uint32 `protobuf:"varint,26,opt,name=committed_major,json=committedMajor,proto3" json:"committed_major,omitempty"` LaunchTcb uint64 `protobuf:"varint,27,opt,name=launch_tcb,json=launchTcb,proto3" json:"launch_tcb,omitempty"` Signature []byte `protobuf:"bytes,28,opt,name=signature,proto3" json:"signature,omitempty"` // Should be 512 bytes long Cpuid1EaxFms uint32 `protobuf:"varint,29,opt,name=cpuid1eax_fms,json=cpuid1eaxFms,proto3" json:"cpuid1eax_fms,omitempty"` // The cpuid(1).eax & 0x0fff0fff representation of family/model/stepping } func (x *Report) Reset() { *x = Report{} if protoimpl.UnsafeEnabled { mi := &file_sevsnp_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Report) String() string { return protoimpl.X.MessageStringOf(x) } func (*Report) ProtoMessage() {} func (x *Report) ProtoReflect() protoreflect.Message { mi := &file_sevsnp_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Report.ProtoReflect.Descriptor instead. func (*Report) Descriptor() ([]byte, []int) { return file_sevsnp_proto_rawDescGZIP(), []int{0} } func (x *Report) GetVersion() uint32 { if x != nil { return x.Version } return 0 } func (x *Report) GetGuestSvn() uint32 { if x != nil { return x.GuestSvn } return 0 } func (x *Report) GetPolicy() uint64 { if x != nil { return x.Policy } return 0 } func (x *Report) GetFamilyId() []byte { if x != nil { return x.FamilyId } return nil } func (x *Report) GetImageId() []byte { if x != nil { return x.ImageId } return nil } func (x *Report) GetVmpl() uint32 { if x != nil { return x.Vmpl } return 0 } func (x *Report) GetSignatureAlgo() uint32 { if x != nil { return x.SignatureAlgo } return 0 } func (x *Report) GetCurrentTcb() uint64 { if x != nil { return x.CurrentTcb } return 0 } func (x *Report) GetPlatformInfo() uint64 { if x != nil { return x.PlatformInfo } return 0 } func (x *Report) GetSignerInfo() uint32 { if x != nil { return x.SignerInfo } return 0 } func (x *Report) GetReportData() []byte { if x != nil { return x.ReportData } return nil } func (x *Report) GetMeasurement() []byte { if x != nil { return x.Measurement } return nil } func (x *Report) GetHostData() []byte { if x != nil { return x.HostData } return nil } func (x *Report) GetIdKeyDigest() []byte { if x != nil { return x.IdKeyDigest } return nil } func (x *Report) GetAuthorKeyDigest() []byte { if x != nil { return x.AuthorKeyDigest } return nil } func (x *Report) GetReportId() []byte { if x != nil { return x.ReportId } return nil } func (x *Report) GetReportIdMa() []byte { if x != nil { return x.ReportIdMa } return nil } func (x *Report) GetReportedTcb() uint64 { if x != nil { return x.ReportedTcb } return 0 } func (x *Report) GetChipId() []byte { if x != nil { return x.ChipId } return nil } func (x *Report) GetCommittedTcb() uint64 { if x != nil { return x.CommittedTcb } return 0 } func (x *Report) GetCurrentBuild() uint32 { if x != nil { return x.CurrentBuild } return 0 } func (x *Report) GetCurrentMinor() uint32 { if x != nil { return x.CurrentMinor } return 0 } func (x *Report) GetCurrentMajor() uint32 { if x != nil { return x.CurrentMajor } return 0 } func (x *Report) GetCommittedBuild() uint32 { if x != nil { return x.CommittedBuild } return 0 } func (x *Report) GetCommittedMinor() uint32 { if x != nil { return x.CommittedMinor } return 0 } func (x *Report) GetCommittedMajor() uint32 { if x != nil { return x.CommittedMajor } return 0 } func (x *Report) GetLaunchTcb() uint64 { if x != nil { return x.LaunchTcb } return 0 } func (x *Report) GetSignature() []byte { if x != nil { return x.Signature } return nil } func (x *Report) GetCpuid1EaxFms() uint32 { if x != nil { return x.Cpuid1EaxFms } return 0 } type CertificateChain struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The versioned chip endorsement key's certificate for the // key that signed this report. VcekCert []byte `protobuf:"bytes,1,opt,name=vcek_cert,json=vcekCert,proto3" json:"vcek_cert,omitempty"` // The versioned loaded endorsement key's certificate for the // key that signed this report. VlekCert []byte `protobuf:"bytes,6,opt,name=vlek_cert,json=vlekCert,proto3" json:"vlek_cert,omitempty"` // The AMD SEV or AMD SEV-VLEK certificate that signed the V?EK cert. AskCert []byte `protobuf:"bytes,2,opt,name=ask_cert,json=askCert,proto3" json:"ask_cert,omitempty"` // The AMD Root key certificate (signs the ASK cert). ArkCert []byte `protobuf:"bytes,3,opt,name=ark_cert,json=arkCert,proto3" json:"ark_cert,omitempty"` // A certificate the host may inject to endorse the measurement of the // firmware. // // Deprecated: Marked as deprecated in sevsnp.proto. FirmwareCert []byte `protobuf:"bytes,4,opt,name=firmware_cert,json=firmwareCert,proto3" json:"firmware_cert,omitempty"` // Non-standard certificates the host may inject. Extras map[string][]byte `protobuf:"bytes,7,rep,name=extras,proto3" json:"extras,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *CertificateChain) Reset() { *x = CertificateChain{} if protoimpl.UnsafeEnabled { mi := &file_sevsnp_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CertificateChain) String() string { return protoimpl.X.MessageStringOf(x) } func (*CertificateChain) ProtoMessage() {} func (x *CertificateChain) ProtoReflect() protoreflect.Message { mi := &file_sevsnp_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CertificateChain.ProtoReflect.Descriptor instead. func (*CertificateChain) Descriptor() ([]byte, []int) { return file_sevsnp_proto_rawDescGZIP(), []int{1} } func (x *CertificateChain) GetVcekCert() []byte { if x != nil { return x.VcekCert } return nil } func (x *CertificateChain) GetVlekCert() []byte { if x != nil { return x.VlekCert } return nil } func (x *CertificateChain) GetAskCert() []byte { if x != nil { return x.AskCert } return nil } func (x *CertificateChain) GetArkCert() []byte { if x != nil { return x.ArkCert } return nil } // Deprecated: Marked as deprecated in sevsnp.proto. func (x *CertificateChain) GetFirmwareCert() []byte { if x != nil { return x.FirmwareCert } return nil } func (x *CertificateChain) GetExtras() map[string][]byte { if x != nil { return x.Extras } return nil } // The CPUID[EAX=1] version information includes product info as described in // the AMD KDS specification. The product name, model, and stepping values are // important for determining the required parameters to KDS when requesting the // endorsement key's certificate. type SevProduct struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name SevProduct_SevProductName `protobuf:"varint,1,opt,name=name,proto3,enum=sevsnp.SevProduct_SevProductName" json:"name,omitempty"` // Deprecated: Marked as deprecated in sevsnp.proto. Stepping uint32 `protobuf:"varint,2,opt,name=stepping,proto3" json:"stepping,omitempty"` // Must be a 4-bit number MachineStepping *wrapperspb.UInt32Value `protobuf:"bytes,3,opt,name=machine_stepping,json=machineStepping,proto3" json:"machine_stepping,omitempty"` } func (x *SevProduct) Reset() { *x = SevProduct{} if protoimpl.UnsafeEnabled { mi := &file_sevsnp_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SevProduct) String() string { return protoimpl.X.MessageStringOf(x) } func (*SevProduct) ProtoMessage() {} func (x *SevProduct) ProtoReflect() protoreflect.Message { mi := &file_sevsnp_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SevProduct.ProtoReflect.Descriptor instead. func (*SevProduct) Descriptor() ([]byte, []int) { return file_sevsnp_proto_rawDescGZIP(), []int{2} } func (x *SevProduct) GetName() SevProduct_SevProductName { if x != nil { return x.Name } return SevProduct_SEV_PRODUCT_UNKNOWN } // Deprecated: Marked as deprecated in sevsnp.proto. func (x *SevProduct) GetStepping() uint32 { if x != nil { return x.Stepping } return 0 } func (x *SevProduct) GetMachineStepping() *wrapperspb.UInt32Value { if x != nil { return x.MachineStepping } return nil } type Attestation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Report *Report `protobuf:"bytes,1,opt,name=report,proto3" json:"report,omitempty"` CertificateChain *CertificateChain `protobuf:"bytes,2,opt,name=certificate_chain,json=certificateChain,proto3" json:"certificate_chain,omitempty"` Product *SevProduct `protobuf:"bytes,3,opt,name=product,proto3" json:"product,omitempty"` } func (x *Attestation) Reset() { *x = Attestation{} if protoimpl.UnsafeEnabled { mi := &file_sevsnp_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Attestation) String() string { return protoimpl.X.MessageStringOf(x) } func (*Attestation) ProtoMessage() {} func (x *Attestation) ProtoReflect() protoreflect.Message { mi := &file_sevsnp_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Attestation.ProtoReflect.Descriptor instead. func (*Attestation) Descriptor() ([]byte, []int) { return file_sevsnp_proto_rawDescGZIP(), []int{3} } func (x *Attestation) GetReport() *Report { if x != nil { return x.Report } return nil } func (x *Attestation) GetCertificateChain() *CertificateChain { if x != nil { return x.CertificateChain } return nil } func (x *Attestation) GetProduct() *SevProduct { if x != nil { return x.Product } return nil } var File_sevsnp_proto protoreflect.FileDescriptor var file_sevsnp_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcd, 0x07, 0x0a, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x67, 0x75, 0x65, 0x73, 0x74, 0x53, 0x76, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x6d, 0x70, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6d, 0x70, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x41, 0x6c, 0x67, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x63, 0x62, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x64, 0x4b, 0x65, 0x79, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x6d, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x4d, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x12, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x54, 0x63, 0x62, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x14, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x63, 0x62, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x54, 0x63, 0x62, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x70, 0x75, 0x69, 0x64, 0x31, 0x65, 0x61, 0x78, 0x5f, 0x66, 0x6d, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x70, 0x75, 0x69, 0x64, 0x31, 0x65, 0x61, 0x78, 0x46, 0x6d, 0x73, 0x22, 0xa4, 0x02, 0x0a, 0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x63, 0x65, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x76, 0x63, 0x65, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x6c, 0x65, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x76, 0x6c, 0x65, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x72, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x72, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x27, 0x0a, 0x0d, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x43, 0x65, 0x72, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x65, 0x78, 0x74, 0x72, 0x61, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x65, 0x78, 0x74, 0x72, 0x61, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x45, 0x78, 0x74, 0x72, 0x61, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9c, 0x02, 0x0a, 0x0a, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x08, 0x73, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x47, 0x0a, 0x10, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x6e, 0x0a, 0x0e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x4d, 0x49, 0x4c, 0x41, 0x4e, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x47, 0x45, 0x4e, 0x4f, 0x41, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x54, 0x55, 0x52, 0x49, 0x4e, 0x10, 0x03, 0x22, 0xaa, 0x01, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x45, 0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x65, 0x76, 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_sevsnp_proto_rawDescOnce sync.Once file_sevsnp_proto_rawDescData = file_sevsnp_proto_rawDesc ) func file_sevsnp_proto_rawDescGZIP() []byte { file_sevsnp_proto_rawDescOnce.Do(func() { file_sevsnp_proto_rawDescData = protoimpl.X.CompressGZIP(file_sevsnp_proto_rawDescData) }) return file_sevsnp_proto_rawDescData } var file_sevsnp_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_sevsnp_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_sevsnp_proto_goTypes = []interface{}{ (SevProduct_SevProductName)(0), // 0: sevsnp.SevProduct.SevProductName (*Report)(nil), // 1: sevsnp.Report (*CertificateChain)(nil), // 2: sevsnp.CertificateChain (*SevProduct)(nil), // 3: sevsnp.SevProduct (*Attestation)(nil), // 4: sevsnp.Attestation nil, // 5: sevsnp.CertificateChain.ExtrasEntry (*wrapperspb.UInt32Value)(nil), // 6: google.protobuf.UInt32Value } var file_sevsnp_proto_depIdxs = []int32{ 5, // 0: sevsnp.CertificateChain.extras:type_name -> sevsnp.CertificateChain.ExtrasEntry 0, // 1: sevsnp.SevProduct.name:type_name -> sevsnp.SevProduct.SevProductName 6, // 2: sevsnp.SevProduct.machine_stepping:type_name -> google.protobuf.UInt32Value 1, // 3: sevsnp.Attestation.report:type_name -> sevsnp.Report 2, // 4: sevsnp.Attestation.certificate_chain:type_name -> sevsnp.CertificateChain 3, // 5: sevsnp.Attestation.product:type_name -> sevsnp.SevProduct 6, // [6:6] is the sub-list for method output_type 6, // [6:6] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name 6, // [6:6] is the sub-list for extension extendee 0, // [0:6] is the sub-list for field type_name } func init() { file_sevsnp_proto_init() } func file_sevsnp_proto_init() { if File_sevsnp_proto != nil { return } if !protoimpl.UnsafeEnabled { file_sevsnp_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Report); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_sevsnp_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CertificateChain); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_sevsnp_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SevProduct); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_sevsnp_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Attestation); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_sevsnp_proto_rawDesc, NumEnums: 1, NumMessages: 5, NumExtensions: 0, NumServices: 0, }, GoTypes: file_sevsnp_proto_goTypes, DependencyIndexes: file_sevsnp_proto_depIdxs, EnumInfos: file_sevsnp_proto_enumTypes, MessageInfos: file_sevsnp_proto_msgTypes, }.Build() File_sevsnp_proto = out.File file_sevsnp_proto_rawDesc = nil file_sevsnp_proto_goTypes = nil file_sevsnp_proto_depIdxs = nil } go-sev-guest-0.13.0/testing/000077500000000000000000000000001476044460400156065ustar00rootroot00000000000000go-sev-guest-0.13.0/testing/client/000077500000000000000000000000001476044460400170645ustar00rootroot00000000000000go-sev-guest-0.13.0/testing/client/client.go000066400000000000000000000156051476044460400207000ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package client (in testing) allows tests to get a fake or real sev-guest device. package client import ( "testing" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/client" test "github.com/google/go-sev-guest/testing" "github.com/google/go-sev-guest/verify/trust" "google.golang.org/protobuf/proto" ) // SkipUnmockableTestCase returns whether we have to skip a mocked failure test case on real hardware. func SkipUnmockableTestCase(tc *test.TestCase) bool { return !client.UseDefaultSevGuest() && tc.FwErr != 0 } // GetSevGuest is a cross-platform testing helper function that retrieves the // appropriate SEV-guest device from the flags passed into "go test". // // If using a test guest device, this will also produce a fake AMD-SP that produces the signed // versions of given attestation reports based on different nonce input. Its returned roots of trust // are based on the fake's signing credentials. func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (client.Device, map[string][]*trust.AMDRootCerts, map[string][]*trust.AMDRootCerts, trust.HTTPSGetter) { tb.Helper() if client.UseDefaultSevGuest() { sevTestDevice, err := test.TcDevice(tcs, opts) if err != nil { tb.Fatalf("failed to create test device: %v", err) } goodSnpRoot := map[string][]*trust.AMDRootCerts{ "Milan": { { Product: "Milan", // TODO(Issue#114): Remove ProductLine: "Milan", ProductCerts: &trust.ProductCerts{ Ask: sevTestDevice.Signer.Ask, Ark: sevTestDevice.Signer.Ark, Asvk: sevTestDevice.Signer.Asvk, }, }, }, } badSnpRoot := map[string][]*trust.AMDRootCerts{ "Milan": { { Product: "Milan", // TODO(Issue#114): Remove ProductLine: "Milan", ProductCerts: &trust.ProductCerts{ // No ASK, oops. Ask: sevTestDevice.Signer.Ark, Ark: sevTestDevice.Signer.Ark, Asvk: sevTestDevice.Signer.Ark, }, }, }, } fakekds, err := test.FakeKDSFromSigner(sevTestDevice.Signer) if err != nil { tb.Fatalf("failed to create fake KDS from signer: %v", err) } return sevTestDevice, goodSnpRoot, badSnpRoot, fakekds } client, err := client.OpenDevice() if err != nil { tb.Fatalf("Failed to open SEV guest device: %v", err) } kdsImpl := test.GetKDS(tb) badSnpRoot := make(map[string][]*trust.AMDRootCerts) for productLine, rootCerts := range trust.DefaultRootCerts { // Supplement the defaults with the missing x509 certificates. pc, err := trust.GetProductChain(productLine, abi.VcekReportSigner, kdsImpl) if err != nil { tb.Fatalf("failed to get product chain for %q: %v", productLine, err) } // By removing the ASK intermediate, we ensure that the attestation will never verify. badSnpRoot[productLine] = []*trust.AMDRootCerts{{ Product: productLine, // TODO(Issue#114): Remove ProductLine: productLine, ProductCerts: &trust.ProductCerts{ Ark: pc.Ark, Ask: pc.Ark, Asvk: pc.Ark, }, AskSev: rootCerts.ArkSev, ArkSev: rootCerts.AskSev, }} } return client, nil, badSnpRoot, kdsImpl } // GetSevQuoteProvider is a cross-platform testing helper function that retrieves the // appropriate SEV-guest device from the flags passed into "go test". // // If using a test guest device, this will also produce a fake AMD-SP that produces the signed // versions of given attestation reports based on different nonce input. Its returned roots of trust // are based on the fake's signing credentials. func GetSevQuoteProvider(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (client.QuoteProvider, map[string][]*trust.AMDRootCerts, map[string][]*trust.AMDRootCerts, trust.HTTPSGetter) { tb.Helper() if client.UseDefaultSevGuest() { sevQp, err := test.TcQuoteProvider(tcs, opts) if err != nil { tb.Fatalf("failed to create test device: %v", err) } goodSnpRoot := map[string][]*trust.AMDRootCerts{ "Milan": { { Product: "Milan", // TODO(Issue#114): Remove ProductLine: "Milan", ProductCerts: &trust.ProductCerts{ Ask: sevQp.Device.Signer.Ask, Ark: sevQp.Device.Signer.Ark, Asvk: sevQp.Device.Signer.Asvk, }, }, }, "Genoa": { { Product: "Genoa", // TODO(Issue#114): Remove ProductLine: "Genoa", ProductCerts: &trust.ProductCerts{ Ask: sevQp.Device.Signer.Ask, Ark: sevQp.Device.Signer.Ark, Asvk: sevQp.Device.Signer.Asvk, }, }, }, } badSnpRoot := map[string][]*trust.AMDRootCerts{ "Milan": { { Product: "Milan", // TODO(Issue#114): Remove ProductLine: "Milan", ProductCerts: &trust.ProductCerts{ // No ASK, oops. Ask: sevQp.Device.Signer.Ark, Ark: sevQp.Device.Signer.Ark, Asvk: sevQp.Device.Signer.Ark, }, }, }, "Genoa": { { Product: "Genoa", // TODO(Issue#114): Remove ProductLine: "Genoa", ProductCerts: &trust.ProductCerts{ // No ASK, oops. Ask: sevQp.Device.Signer.Ark, Ark: sevQp.Device.Signer.Ark, Asvk: sevQp.Device.Signer.Ark, }, }, }, } fakekds, err := test.FakeKDSFromSigner(sevQp.Device.Signer) if err != nil { tb.Fatalf("failed to create fake KDS from signer: %v", err) } return sevQp, goodSnpRoot, badSnpRoot, fakekds } // If requested to use a different product than on the machine, fail. if opts.Product != nil && !proto.Equal(abi.SevProduct(), opts.Product) { return nil, nil, nil, nil } client, err := client.GetQuoteProvider() if err != nil { tb.Fatalf("Failed to open SEV guest device: %v", err) } kdsImpl := test.GetKDS(tb) badSnpRoot := make(map[string][]*trust.AMDRootCerts) for productLine, rootCerts := range trust.DefaultRootCerts { // Supplement the defaults with the missing x509 certificates. pc, err := trust.GetProductChain(productLine, abi.VcekReportSigner, kdsImpl) if err != nil { tb.Fatalf("failed to get product chain for %q: %v", productLine, err) } // By removing the ASK intermediate, we ensure that the attestation will never verify. badSnpRoot[productLine] = []*trust.AMDRootCerts{{ Product: productLine, // TODO(Issue#114): Remove ProductLine: productLine, ProductCerts: &trust.ProductCerts{ Ark: pc.Ark, Ask: pc.Ark, Asvk: pc.Ark, }, AskSev: rootCerts.ArkSev, ArkSev: rootCerts.AskSev, }} } return client, nil, badSnpRoot, kdsImpl } go-sev-guest-0.13.0/testing/data/000077500000000000000000000000001476044460400165175ustar00rootroot00000000000000go-sev-guest-0.13.0/testing/data/data.go000066400000000000000000000062551476044460400177670ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package data (in testing) allows tests to access data for testing purpose. package data import ( "crypto/ecdsa" "crypto/rsa" "crypto/x509" _ "embed" "encoding/pem" "log" ) //go:embed keys/vcek_private_key.pem var vcekPrivateKeyPEM []byte //go:embed keys/vlek_private_key.pem var vlekPrivateKeyPEM []byte //go:embed keys/ark_private_key.pem var arkPrivateKeyPEM []byte //go:embed keys/ask_private_key.pem var askPrivateKeyPEM []byte //go:embed keys/asvk_private_key.pem var asvkPrivateKeyPEM []byte // VCEKPrivateKey is the ECDSA private key using P-384 curve with a SHA384 digest for VCEK // Generated using: // // openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt var VCEKPrivateKey = mustParseECDSAPrivateKey(vcekPrivateKeyPEM) // VLEKPrivateKey is the ECDSA private key using P-384 curve with a SHA384 digest for VLEK // Generated using: // // openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt var VLEKPrivateKey = mustParseECDSAPrivateKey(vlekPrivateKeyPEM) // ARKPrivateKey is the RSA private key using 4096-bit length with a SHA256 digest for ARK // Generated using: // // openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt var ARKPrivateKey = mustParseRSAPrivateKey(arkPrivateKeyPEM) // ASKPrivateKey is the ECDSA private key using 4096-bit length with a SHA256 digest for ASK // Generated using: // // openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt var ASKPrivateKey = mustParseRSAPrivateKey(askPrivateKeyPEM) // ASVKPrivateKey is the ECDSA private key using 4096-bit length with a SHA256 digest for ASVK // Generated using: // // openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt var ASVKPrivateKey = mustParseRSAPrivateKey(asvkPrivateKeyPEM) func mustParseECDSAPrivateKey(pemBytes []byte) *ecdsa.PrivateKey { privateKey := mustParsePKCS8PrivateKey(pemBytes) ecPrivateKey, ok := privateKey.(*ecdsa.PrivateKey) if !ok { log.Fatalf("Unexpected private key type, want ECDSA private key") } return ecPrivateKey } func mustParseRSAPrivateKey(pemBytes []byte) *rsa.PrivateKey { privateKey := mustParsePKCS8PrivateKey(pemBytes) rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey) if !ok { log.Fatalf("Unexpected private key type, want RSA private key") } return rsaPrivateKey } func mustParsePKCS8PrivateKey(pemBytes []byte) any { block, rest := pem.Decode(pemBytes) if block == nil { log.Fatal("Unable to decode key as PEM") } if len(rest) > 0 { log.Fatal("Unexpected trailing data in key file") } privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { log.Fatalf("Unable to parse PKCS8 private key: %v", err) } return privateKey } go-sev-guest-0.13.0/testing/data/keys/000077500000000000000000000000001476044460400174725ustar00rootroot00000000000000go-sev-guest-0.13.0/testing/data/keys/ark_private_key.pem000066400000000000000000000063101476044460400233540ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- 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 MkegxleNCeJpIn1Wsj7a0gVy2ub1Lg== -----END PRIVATE KEY----- go-sev-guest-0.13.0/testing/data/keys/ask_private_key.pem000066400000000000000000000063101476044460400233550ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- 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 MDTH4Mh5L36CtDc2VZJhlsKpBIwskqM= -----END PRIVATE KEY----- go-sev-guest-0.13.0/testing/data/keys/asvk_private_key.pem000066400000000000000000000063101476044460400235430ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- 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 nbAMwwicy/ojYIpDbKmVFEnX3Zy4TA== -----END PRIVATE KEY----- go-sev-guest-0.13.0/testing/data/keys/vcek_private_key.pem000066400000000000000000000004621476044460400235310ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAnWpLdR3Yr2nV7M1dy HSCUp2ZMlIP50hdvOkkPNTEiuLYxhQkxXS/WO0/nfdGdQO2hZANiAAQ2I63wh+Ud nxnPb6Izu8aAH0qFFrF04n0YK1PjLl1lZgdsZQZWrtOnSr7dQtE300gtjBiBlGGB 0k2MuCt+OsrYgXeN+yTWfe52V28BvacmJedYbmTViN0hXhm6+1h1AU4= -----END PRIVATE KEY----- go-sev-guest-0.13.0/testing/data/keys/vlek_private_key.pem000066400000000000000000000004621476044460400235420ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBunPPtTnRUcgPCyam+ Hrd6HkvDkDlciDPB04JX/V5e4LMd9Tp1NdNXiXQ6vA/hal2hZANiAAQsWkZc2y+J /oNbImA+tjkuORiCNzo84vz5zoIg3vT49hpTtP46r+30dVZVbN/GYBAqhi680De5 qXjOW5ZdvtXhEVi0G6AYWJuh/h/bAQyNeMQACFoPaYkwIlcGXHMnD3g= -----END PRIVATE KEY----- go-sev-guest-0.13.0/testing/fake_certs.go000066400000000000000000000437351476044460400202570ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package testing defines fakes and mocks for the sev-guest device and AMD-SP. package testing import ( "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "flag" "fmt" "math/big" "strings" "testing" // Insecure randomness for faster testing. "math/rand" "time" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" spb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/go-sev-guest/testing/data" "github.com/google/uuid" ) // KDS specification: // https://www.amd.com/system/files/TechDocs/57230.pdf const ( arkExpirationYears = 25 askExpirationYears = 25 asvkExpirationYears = 25 vcekExpirationYears = 7 ) var ( // Product decides the expected product for attestation report validation. If empty, inferred // to be the ProductLine of --product_name. Product = flag.String("product", "", "The product string for the SEV-SNP machine tested on. The stepping version is ignored.") // ProductName decides the fake certificates' product name. It must be parsable by // kds.ParseProductName. The flag may also be used to direct the hardware verification options. // If empty, defined to be kds.ProductName(abi.DefaultSevProduct()). ProductName = flag.String("product_name", "", "The product name for the SEV-SNP machine tested on. Overrides --product.") ) // GetProductName returns the --product_name flag value or a valid Default. func GetProductName() string { if *ProductName == "" { return kds.ProductName(abi.DefaultSevProduct()) } return *ProductName } // GetProductLine returns the actual or inferred value of --product. func GetProductLine() string { if *Product == "" { return kds.ProductLineOfProductName(GetProductName()) } return *Product } // GetProduct returns the expected product for validation. func GetProduct(t testing.TB) *spb.SevProduct { if *Product == "ignore" { return nil } // If a specific product name is not given, then use the product line. if *ProductName == "" { product, err := kds.ParseProductLine(GetProductLine()) if err != nil { t.Fatalf("ParseProductLine(%s) = _, %v errored unexpectedly", GetProductLine(), err) } return product } product, err := kds.ParseProductName(*ProductName, abi.VcekReportSigner) if err != nil { t.Fatalf("ParseProductName(%s) = _, %v errored unexpectedly", *ProductName, err) } return product } // AmdSigner encapsulates a key and certificate chain following the format of AMD-SP's VCEK for // signing attestation reports. type AmdSigner struct { Ark *x509.Certificate Ask *x509.Certificate Asvk *x509.Certificate Vcek *x509.Certificate Vlek *x509.Certificate Extras map[string][]byte Keys *AmdKeys // This identity does not match AMD's notion of an HWID. It is purely to combine expectations of // report data -> KDS URL construction for the fake KDS implementation. HWID [abi.ChipIDSize]byte TCB kds.TCBVersion Product *spb.SevProduct } // AmdKeys encapsulates the key chain of ARK through ASK down to VCEK. type AmdKeys struct { Ark *rsa.PrivateKey Ask *rsa.PrivateKey Asvk *rsa.PrivateKey Vcek *ecdsa.PrivateKey Vlek *ecdsa.PrivateKey } var insecureRandomness = rand.New(rand.NewSource(0xc0de)) // Sign takes a chunk of bytes, signs it with VcekPriv, and returns the R, S pair for the signature // in little endian format. func (s *AmdSigner) Sign(toSign []byte) (*big.Int, *big.Int, error) { info, err := abi.ReportSignerInfo(toSign) if err != nil { return nil, nil, err } si, err := abi.ParseSignerInfo(info) if err != nil { return nil, nil, err } var key *ecdsa.PrivateKey switch si.SigningKey { case abi.VcekReportSigner: key = s.Keys.Vcek case abi.VlekReportSigner: key = s.Keys.Vlek } h := crypto.SHA384.New() h.Write(toSign) R, S, err := ecdsa.Sign(insecureRandomness, key, h.Sum(nil)) if err != nil { return nil, nil, err } return R, S, nil } // CertOverride encapsulates certificate aspects that can be overridden when creating a certificate // chain. type CertOverride struct { // If 0, interpreted as Version, otherwise the ARK cert version number. Version int SerialNumber *big.Int Issuer *pkix.Name Subject *pkix.Name SignatureAlgorithm x509.SignatureAlgorithm PublicKeyAlgorithm x509.PublicKeyAlgorithm KeyUsage x509.KeyUsage // If nil, interpreted as default, otherwise the CRLDistributionPoints for the cert. CRLDistributionPoints []string // If nil, interpreted as default list. Extensions []pkix.Extension } // AmdSignerBuilder represents toggleable configurations of the VCEK certificate chain. type AmdSignerBuilder struct { // Keys contains the private keys that will get a certificate chain structure. Keys *AmdKeys ProductName string ArkCreationTime time.Time AskCreationTime time.Time AsvkCreationTime time.Time VcekCreationTime time.Time VlekCreationTime time.Time ArkCustom CertOverride AskCustom CertOverride AsvkCustom CertOverride VcekCustom CertOverride VlekCustom CertOverride CSPID string HWID [abi.ChipIDSize]byte TCB kds.TCBVersion // Intermediate built certificates Ark *x509.Certificate Ask *x509.Certificate Asvk *x509.Certificate Vcek *x509.Certificate Vlek *x509.Certificate Extras map[string][]byte } func (b *AmdSignerBuilder) productName() string { if b.ProductName == "" { return GetProductName() } return b.ProductName } func (b *AmdSignerBuilder) productLine() string { return kds.ProductLineOfProductName(b.productName()) } func amdPkixName(commonName string, serialNumber string) pkix.Name { return pkix.Name{ Organization: []string{"Advanced Micro Devices"}, Country: []string{"US"}, OrganizationalUnit: []string{"Engineering"}, Locality: []string{"Santa Clara"}, Province: []string{"CA"}, SerialNumber: serialNumber, CommonName: commonName, } } func arkName(productLine, serialNumber string) pkix.Name { return amdPkixName(fmt.Sprintf("ARK-%s", productLine), serialNumber) } func askName(productLine, serialNumber string) pkix.Name { return amdPkixName(fmt.Sprintf("SEV-%s", productLine), serialNumber) } func asvkName(productLine, serialNumber string) pkix.Name { return amdPkixName(fmt.Sprintf("SEV-VLEK-%s", productLine), serialNumber) } func (b *AmdSignerBuilder) unsignedRoot(arkName pkix.Name, key abi.ReportSigner, subjectSerial *big.Int, creationTime time.Time, expirationYears int) *x509.Certificate { var subject pkix.Name issuer := arkName cert := &x509.Certificate{} crl := kds.CrlLinkByKey(b.productLine(), key) sn := fmt.Sprintf("%x", subjectSerial) switch key { case abi.VcekReportSigner: subject = askName(b.productLine(), sn) case abi.VlekReportSigner: subject = asvkName(b.productLine(), sn) case abi.NoneReportSigner: crl = kds.CrlLinkByKey(b.productLine(), abi.VcekReportSigner) subject = arkName } cert.NotBefore = creationTime cert.NotAfter = creationTime.Add(time.Duration(365*24*expirationYears) * time.Hour) cert.SignatureAlgorithm = x509.SHA384WithRSAPSS cert.PublicKeyAlgorithm = x509.RSA cert.Version = 3 cert.SerialNumber = subjectSerial cert.Issuer = issuer cert.Subject = subject cert.CRLDistributionPoints = []string{crl} cert.IsCA = true cert.BasicConstraintsValid = true return cert } func (o CertOverride) override(cert *x509.Certificate) *x509.Certificate { if o.SignatureAlgorithm != x509.UnknownSignatureAlgorithm { cert.SignatureAlgorithm = o.SignatureAlgorithm } if o.PublicKeyAlgorithm != x509.UnknownPublicKeyAlgorithm { cert.PublicKeyAlgorithm = o.PublicKeyAlgorithm } if o.Version != 0 { cert.Version = o.Version } if o.Issuer != nil { cert.Issuer = *o.Issuer } if o.Subject != nil { cert.Subject = *o.Subject } if o.SerialNumber != nil { cert.SerialNumber = o.SerialNumber cert.Subject.SerialNumber = fmt.Sprintf("%x", o.SerialNumber) } if o.KeyUsage != x509.KeyUsage(0) { cert.KeyUsage = o.KeyUsage } if o.CRLDistributionPoints != nil { cert.CRLDistributionPoints = o.CRLDistributionPoints } if o.Extensions != nil { cert.ExtraExtensions = o.Extensions } return cert } // DefaultArk returns a new RSA key with the expected size for an ARK. func DefaultArk() *rsa.PrivateKey { return data.ARKPrivateKey } // DefaultAsk returns a new RSA key with the expected size for an ASK. func DefaultAsk() *rsa.PrivateKey { return data.ASKPrivateKey } // DefaultAsvk returns a new RSA key with the expected size for an ASVK. func DefaultAsvk() *rsa.PrivateKey { return data.ASVKPrivateKey } // DefaultVcek returns a new ECDSA key on the expected curve for a VCEK. func DefaultVcek() *ecdsa.PrivateKey { return data.VCEKPrivateKey } // DefaultVlek returns a new ECDSA key on the expected curve for a VLEK. func DefaultVlek() *ecdsa.PrivateKey { return data.VLEKPrivateKey } // DefaultAmdKeys returns a key set for ARK, ASK, and VCEK with the expected key type and size. func DefaultAmdKeys() *AmdKeys { return &AmdKeys{ Ark: DefaultArk(), Ask: DefaultAsk(), Vcek: DefaultVcek(), Vlek: DefaultVlek(), Asvk: DefaultAsvk(), } } func (b *AmdSignerBuilder) certifyArk() error { sn := big.NewInt(0xc0dec0de) name := arkName(b.productLine(), fmt.Sprintf("%x", sn)) cert := b.unsignedRoot(name, abi.NoneReportSigner, sn, b.ArkCreationTime, arkExpirationYears) cert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign b.ArkCustom.override(cert) caBytes, err := x509.CreateCertificate(insecureRandomness, cert, cert, b.Keys.Ark.Public(), b.Keys.Ark) if err != nil { return fmt.Errorf("could not create a certificate from %v: %v", cert, err) } signed, err := x509.ParseCertificate(caBytes) b.Ark = signed return err } // must be called after certifyArk func (b *AmdSignerBuilder) certifyAsk() error { sn := big.NewInt(0xc0dec0de) cert := b.unsignedRoot(b.Ark.Subject, abi.VcekReportSigner, sn, b.AskCreationTime, askExpirationYears) cert.KeyUsage = x509.KeyUsageCertSign b.AskCustom.override(cert) caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Ark, b.Keys.Ask.Public(), b.Keys.Ark) if err != nil { return fmt.Errorf("could not create a certificate from %v: %v", cert, err) } askcert, err := x509.ParseCertificate(caBytes) if err != nil { return err } b.Ask = askcert return err } // must be called after certifyArk func (b *AmdSignerBuilder) certifyAsvk() error { sn := big.NewInt(0xc0dec0de) cert := b.unsignedRoot(b.Ark.Subject, abi.VlekReportSigner, sn, b.AsvkCreationTime, asvkExpirationYears) cert.KeyUsage = x509.KeyUsageCertSign b.AsvkCustom.override(cert) caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Ark, b.Keys.Asvk.Public(), b.Keys.Ark) if err != nil { return fmt.Errorf("could not create a certificate from %v: %v", cert, err) } asvkcert, err := x509.ParseCertificate(caBytes) if err != nil { return err } b.Asvk = asvkcert return err } // CustomExtensions returns an array of extensions following the KDS specification // for the given values. func CustomExtensions(tcb kds.TCBParts, hwid []byte, cspid, productName string) []pkix.Extension { var productNameAsn1 []byte asn1Zero, _ := asn1.Marshal(0) if hwid != nil { productNameAsn1, _ = asn1.MarshalWithParams(productName, "ia5") } else { parts := strings.SplitN(productName, "-", 2) // VLEK doesn't have a -stepping component to its productName. productNameAsn1, _ = asn1.MarshalWithParams(parts[0], "ia5") } blSpl, _ := asn1.Marshal(int(tcb.BlSpl)) teeSpl, _ := asn1.Marshal(int(tcb.TeeSpl)) snpSpl, _ := asn1.Marshal(int(tcb.SnpSpl)) spl4, _ := asn1.Marshal(int(tcb.Spl4)) spl5, _ := asn1.Marshal(int(tcb.Spl5)) spl6, _ := asn1.Marshal(int(tcb.Spl6)) spl7, _ := asn1.Marshal(int(tcb.Spl7)) ucodeSpl, _ := asn1.Marshal(int(tcb.UcodeSpl)) exts := []pkix.Extension{ {Id: kds.OidStructVersion, Value: asn1Zero}, {Id: kds.OidProductName1, Value: productNameAsn1}, {Id: kds.OidBlSpl, Value: blSpl}, {Id: kds.OidTeeSpl, Value: teeSpl}, {Id: kds.OidSnpSpl, Value: snpSpl}, {Id: kds.OidSpl4, Value: spl4}, {Id: kds.OidSpl5, Value: spl5}, {Id: kds.OidSpl6, Value: spl6}, {Id: kds.OidSpl7, Value: spl7}, {Id: kds.OidUcodeSpl, Value: ucodeSpl}, } if hwid != nil { asn1Hwid, _ := asn1.Marshal(hwid[:]) exts = append(exts, pkix.Extension{Id: kds.OidHwid, Value: asn1Hwid}) } else { if cspid == "" { cspid = "placeholder" } asn1cspid, _ := asn1.MarshalWithParams(cspid, "ia5") exts = append(exts, pkix.Extension{Id: kds.OidCspID, Value: asn1cspid}) } return exts } func (b *AmdSignerBuilder) endorsementKeyPrecert(creationTime time.Time, hwid []byte, serialNumber *big.Int, key abi.ReportSigner) *x509.Certificate { subject := amdPkixName(fmt.Sprintf("SEV-%s", key.String()), "0") subject.SerialNumber = fmt.Sprintf("%x", serialNumber) ica := b.Ask if key == abi.VlekReportSigner { ica = b.Asvk } return &x509.Certificate{ Version: 3, SignatureAlgorithm: x509.SHA384WithRSAPSS, PublicKeyAlgorithm: x509.ECDSA, Issuer: amdPkixName(fmt.Sprintf("SEV-%s", b.productLine()), ica.Subject.SerialNumber), Subject: subject, SerialNumber: serialNumber, NotBefore: time.Time{}, NotAfter: creationTime.Add(vcekExpirationYears * 365 * 24 * time.Hour), ExtraExtensions: CustomExtensions(kds.TCBParts{}, hwid, b.CSPID, b.productName()), } } func (b *AmdSignerBuilder) certifyVcek() error { cert := b.endorsementKeyPrecert(b.VcekCreationTime, make([]byte, abi.ChipIDSize), big.NewInt(0), abi.VcekReportSigner) b.VcekCustom.override(cert) caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Ask, b.Keys.Vcek.Public(), b.Keys.Ask) if err != nil { return fmt.Errorf("could not create a certificate from %v: %v", cert, err) } signed, err := x509.ParseCertificate(caBytes) b.Vcek = signed return err } func (b *AmdSignerBuilder) certifyVlek() error { cert := b.endorsementKeyPrecert(b.VlekCreationTime, nil, big.NewInt(0), abi.VlekReportSigner) b.VlekCustom.override(cert) caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Asvk, b.Keys.Vlek.Public(), b.Keys.Asvk) if err != nil { return fmt.Errorf("could not create a certificate from %v: %v", cert, err) } signed, err := x509.ParseCertificate(caBytes) b.Vlek = signed return err } // TestOnlyCertChain creates a test-only certificate chain from the keys and configurables in b. func (b *AmdSignerBuilder) TestOnlyCertChain() (*AmdSigner, error) { if b.Keys == nil { b.Keys = DefaultAmdKeys() } if err := b.certifyArk(); err != nil { return nil, fmt.Errorf("ark creation error: %v", err) } if err := b.certifyAsk(); err != nil { return nil, fmt.Errorf("ask creation error: %v", err) } if err := b.certifyAsvk(); err != nil { return nil, fmt.Errorf("asvk creation error: %v", err) } if err := b.certifyVcek(); err != nil { return nil, fmt.Errorf("vcek creation error: %v", err) } if b.Keys.Vlek != nil { if err := b.certifyVlek(); err != nil { return nil, fmt.Errorf("vlek creation error: %v", err) } } s := &AmdSigner{ Ark: b.Ark, Ask: b.Ask, Asvk: b.Asvk, Vcek: b.Vcek, Vlek: b.Vlek, Keys: b.Keys, Extras: b.Extras, TCB: b.TCB, } copy(s.HWID[:], b.HWID[:]) return s, nil } // DefaultTestOnlyCertChain creates a test-only certificate chain for a fake attestation signer. func DefaultTestOnlyCertChain(productName string, creationTime time.Time) (*AmdSigner, error) { keys := DefaultAmdKeys() b := &AmdSignerBuilder{ Keys: keys, ProductName: productName, CSPID: "go-sev-guest", ArkCreationTime: creationTime, AskCreationTime: creationTime, AsvkCreationTime: creationTime, VcekCreationTime: creationTime, VlekCreationTime: creationTime, } return b.TestOnlyCertChain() } // CertTableBytes outputs the certificates in AMD's ABI format. func (s *AmdSigner) CertTableBytes() ([]byte, error) { // Calculate the output size and the offset at which to copy each certificate. const baseEntries = 6 // ARK, ASK, VCEK, VLEK, ASVK, NULL entries := baseEntries + len(s.Extras) headers := make([]abi.CertTableHeaderEntry, entries) headers[0].GUID = uuid.MustParse(abi.ArkGUID) headers[0].Offset = uint32(len(headers) * abi.CertTableEntrySize) headers[0].Length = uint32(len(s.Ark.Raw)) headers[1].GUID = uuid.MustParse(abi.AskGUID) headers[1].Offset = headers[0].Offset + headers[0].Length headers[1].Length = uint32(len(s.Ask.Raw)) headers[2].GUID = uuid.MustParse(abi.VcekGUID) headers[2].Offset = headers[1].Offset + headers[1].Length headers[2].Length = uint32(len(s.Vcek.Raw)) headers[3].GUID = uuid.MustParse(abi.VlekGUID) headers[3].Offset = headers[2].Offset + headers[2].Length headers[3].Length = uint32(len(s.Vlek.Raw)) headers[4].GUID = uuid.MustParse(abi.AsvkGUID) headers[4].Offset = headers[3].Offset + headers[3].Length headers[4].Length = uint32(len(s.Asvk.Raw)) index := 4 blobs := [][]byte{s.Ark.Raw, s.Ask.Raw, s.Vcek.Raw, s.Vlek.Raw, s.Asvk.Raw} for guid, data := range s.Extras { prior := index index++ headers[index].GUID = uuid.MustParse(guid) headers[index].Offset = headers[prior].Offset + headers[prior].Length headers[index].Length = uint32(len(data)) blobs = append(blobs, data) } // Write out the headers and the certificates at the appropriate offsets. result := make([]byte, headers[index].Offset+headers[index].Length) for i, cert := range blobs { if err := (&headers[i]).Write(result[i*abi.CertTableEntrySize:]); err != nil { return nil, err } copy(result[headers[i].Offset:], cert) } return result, nil } go-sev-guest-0.13.0/testing/fake_certs_test.go000066400000000000000000000061331476044460400213050ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testing import ( "bytes" "crypto/x509" "testing" "time" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" "github.com/google/uuid" ) func TestCertificatesParse(t *testing.T) { signer, err := DefaultTestOnlyCertChain("Milan", time.Now()) if err != nil { t.Fatal(err) } certBytes, err := signer.CertTableBytes() if err != nil { t.Fatal(err) } entries, err := abi.ParseSnpCertTableHeader(certBytes) if err != nil { t.Fatal(err) } var hasVcek bool var hasVlek bool var hasAsk bool var hasAsvk bool var hasArk bool if len(entries) != 5 { t.Errorf("ParseSnpCertTableHeader(_) returned %d entries, want 5", len(entries)) } for _, entry := range entries { if entry.GUID == uuid.MustParse(abi.VlekGUID) { hasVlek = true } if entry.GUID == uuid.MustParse(abi.VcekGUID) { hasVcek = true } if entry.GUID == uuid.MustParse(abi.AskGUID) { hasAsk = true } if entry.GUID == uuid.MustParse(abi.AsvkGUID) { hasAsvk = true } if entry.GUID == uuid.MustParse(abi.ArkGUID) { hasArk = true } der := certBytes[entry.Offset : entry.Offset+entry.Length] if _, err := x509.ParseCertificate(der); err != nil { t.Errorf("could not parse certificate of %v: %v", entry.GUID, err) } } if !hasVlek { t.Errorf("fake certs missing VLEK") } if !hasVcek { t.Errorf("fake certs missing VCEK") } if !hasAsk { t.Errorf("fake certs missing ASK") } if !hasArk { t.Errorf("fake certs missing ARK") } if !hasAsvk { t.Errorf("fake certs missing ASVK") } if _, err := kds.VcekCertificateExtensions(signer.Vcek); err != nil { t.Errorf("could not parse generated VCEK extensions: %v", err) } } func TestCertificatesExtras(t *testing.T) { b := &AmdSignerBuilder{ Extras: map[string][]byte{abi.ExtraPlatformInfoGUID: []byte("test")}, } s, err := b.TestOnlyCertChain() if err != nil { t.Fatal(err) } certBytes, err := s.CertTableBytes() if err != nil { t.Fatal(err) } entries, err := abi.ParseSnpCertTableHeader(certBytes) if err != nil { t.Fatal(err) } var hasXtra bool if len(entries) != 6 { t.Errorf("ParseSnpCertTableHeader(_) returned %d entries, want 6", len(entries)) } for _, entry := range entries { if entry.GUID == uuid.MustParse(abi.ExtraPlatformInfoGUID) { hasXtra = true got := certBytes[entry.Offset : entry.Offset+entry.Length] want := []byte("test") if !bytes.Equal(got, want) { t.Errorf("%v data is %v, want %v", abi.ExtraPlatformInfoGUID, got, want) } } } if !hasXtra { t.Errorf("fake certs missing extra cert") } } go-sev-guest-0.13.0/testing/fakekds.go000066400000000000000000000157421476044460400175560ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testing import ( "bytes" "encoding/pem" "flag" "fmt" "os" "strings" "testing" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" kpb "github.com/google/go-sev-guest/proto/fakekds" "github.com/google/go-sev-guest/verify/trust" "go.uber.org/multierr" "google.golang.org/protobuf/proto" ) var testUseKDS = flag.Bool("test_use_kds", false, "Deprecated: If true, tests will attempt to retrieve certificates from AMD KDS") type testKdsType struct { value string } func (t *testKdsType) String() string { return t.value } func (t *testKdsType) Set(value string) error { if value != "amd" && value != "cache" && value != "none" { return fmt.Errorf("--test_kds must be one of amd, cache, or none. Got %q", value) } t.value = value return nil } var testKds = testKdsType{value: "cache"} func init() { flag.Var(&testKds, "test_kds", "One of amd, cache, none. If amd, tests will "+ "attempt to retrieve certificates from AMD KDS. If cache, only embedded certificates "+ "will be available given a hostname and TCB version. If none, then no VCEK certificates will "+ "be retrieved.") } // TestUseKDS returns whether tests should use the network to connect the live AMD Key Distribution // service. func TestUseKDS() bool { return *testUseKDS || testKds.value == "amd" } // Insert your own KDS cache here with go:embed. var internalKDSCache []byte // RootBundle represents the two different CA bundles that the KDS can // return. type RootBundle struct { VcekBundle string VlekBundle string } // FakeKDS implements the verify.HTTPSGetter interface to provide certificates like AMD KDS, but // with certificates cached in a protobuf. type FakeKDS struct { Certs *kpb.Certificates // Two CERTIFICATE PEMs for ASK, then ARK or ASVK then ARK, per product RootBundles map[string]*RootBundle } // FakeKDSFromFile returns a FakeKDS from a path to a serialized fakekds.Certificates message. func FakeKDSFromFile(path string) (*FakeKDS, error) { result := &FakeKDS{ Certs: &kpb.Certificates{}, RootBundles: map[string]*RootBundle{ "Milan": { VcekBundle: string(trust.AskArkMilanVcekBytes), VlekBundle: string(trust.AskArkMilanVlekBytes), }, "Genoa": { VcekBundle: string(trust.AskArkGenoaVcekBytes), VlekBundle: string(trust.AskArkGenoaVlekBytes), }, "Turin": { VcekBundle: string(trust.AskArkTurinVcekBytes), VlekBundle: string(trust.AskArkTurinVlekBytes), }}, } contents, err := os.ReadFile(path) if os.IsNotExist(err) { return result, err } if err != nil { return nil, fmt.Errorf("could not load FakeKDS file %q: %v", path, err) } if err := proto.Unmarshal(contents, result.Certs); err != nil { return nil, fmt.Errorf("could not unmarshal FakeKDS file %q: %v", path, err) } return result, nil } // FakeKDSFromSigner returns a FakeKDS that produces the fake signer's certificates following the // AMD KDS REST API expectations. func FakeKDSFromSigner(signer *AmdSigner) (*FakeKDS, error) { certs := &kpb.Certificates{} rootBundles := map[string]*RootBundle{} certs.ChipCerts = []*kpb.Certificates_ChipTCBCerts{ { ChipId: signer.HWID[:], TcbCerts: map[uint64][]byte{ uint64(signer.TCB): signer.Vcek.Raw, }, Fms: abi.MaskedCpuid1EaxFromSevProduct(signer.Product), }, } productLine := kds.ProductLine(signer.Product) b := &strings.Builder{} if err := multierr.Combine( pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ask.Raw}), pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ark.Raw}), ); err != nil { return nil, fmt.Errorf("could not encode VCEK root certificates: %v", err) } rootBundles[productLine] = &RootBundle{VcekBundle: b.String()} if signer.Asvk != nil { b := &strings.Builder{} if err := multierr.Combine( pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Asvk.Raw}), pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ark.Raw}), ); err != nil { return nil, fmt.Errorf("could not encode VLEK root certificates: %v", err) } rootBundles[productLine].VlekBundle = b.String() } return &FakeKDS{Certs: certs, RootBundles: rootBundles}, nil } // FindChipTcbCerts returns the TcbCerts associated with the given chipID in the database if they // exist. If not, returns nil. func FindChipTcbCerts(database *kpb.Certificates, chipID []byte) map[uint64][]byte { for _, cert := range database.ChipCerts { if bytes.Equal(cert.ChipId, chipID) { return cert.TcbCerts } } return nil } // Get translates a KDS url into the expected certificate as represented in the fake's certificate // database. func (f *FakeKDS) Get(url string) ([]byte, error) { // If a root cert request, return the embedded default root certs. product, key, err := kds.ParseProductCertChainURL(url) if err == nil { bundles, ok := f.RootBundles[product] if !ok { return nil, fmt.Errorf("no embedded CA bundle for product %q", product) } switch key { case kds.VcekCertFunction: return []byte(bundles.VcekBundle), nil case kds.VlekCertFunction: return []byte(bundles.VlekBundle), nil default: return nil, fmt.Errorf("internal: unsupperted key type for fake bundles: %q", key) } } vcek, err := kds.ParseVCEKCertURL(url) if err != nil { return nil, err } certs := FindChipTcbCerts(f.Certs, vcek.HWID) if certs == nil { return nil, fmt.Errorf("no certificate found at %q (unknown HWID %v)", url, vcek.HWID) } certbytes, ok := certs[vcek.TCB] if !ok { return nil, fmt.Errorf("no certificate found at %q (host present, bad TCB %v)", url, vcek.TCB) } return certbytes, nil } // GetKDS returns an HTTPSGetter that can produce the expected certificates for a given URL in the // test environment. func GetKDS(t testing.TB) trust.HTTPSGetter { if TestUseKDS() { return trust.DefaultHTTPSGetter() } fakeKds := &FakeKDS{ Certs: &kpb.Certificates{}, RootBundles: map[string]*RootBundle{"Milan": { VcekBundle: string(trust.AskArkMilanVcekBytes), VlekBundle: string(trust.AskArkMilanVlekBytes), }, "Genoa": { VcekBundle: string(trust.AskArkGenoaVcekBytes), VlekBundle: string(trust.AskArkGenoaVlekBytes), }, "Turin": { VcekBundle: string(trust.AskArkTurinVcekBytes), VlekBundle: string(trust.AskArkTurinVlekBytes), }}, } // Provide nothing if --test_kds=none. if testKds.value == "none" { return fakeKds } if err := proto.Unmarshal(internalKDSCache, fakeKds.Certs); err != nil { t.Fatalf("could not unmarshal embedded FakeKDS file: %v", err) } return fakeKds } go-sev-guest-0.13.0/testing/match.go000066400000000000000000000015061476044460400172330ustar00rootroot00000000000000// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testing import "strings" // Match returns true iff both errors match expectations closely enough func Match(got error, want string) bool { if got == nil { return want == "" } return want != "" && strings.Contains(got.Error(), want) } go-sev-guest-0.13.0/testing/mocks.go000066400000000000000000000175171476044460400172640ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testing import ( "encoding/hex" "errors" "fmt" "syscall" "testing" "github.com/google/go-sev-guest/abi" labi "github.com/google/go-sev-guest/client/linuxabi" spb "github.com/google/go-sev-guest/proto/sevsnp" "golang.org/x/sys/unix" ) // GetReportResponse represents a mocked response to a command request. type GetReportResponse struct { Resp labi.SnpReportRespABI EsResult labi.EsResult FwErr abi.SevFirmwareStatus } // Device represents a sev-guest driver implementation with pre-programmed responses to commands. type Device struct { isOpen bool ReportDataRsp map[string]any Keys map[string][]byte Certs []byte Signer *AmdSigner SevProduct *spb.SevProduct } // Open changes the mock device's state to open. func (d *Device) Open(_ string) error { if d.isOpen { return errors.New("device already open") } d.isOpen = true return nil } // Close changes the mock device's state to closed. func (d *Device) Close() error { if !d.isOpen { return errors.New("device already closed") } d.isOpen = false return nil } func (d *Device) getReport(req *labi.SnpReportReqABI, rsp *labi.SnpReportRespABI, fwErr *uint64) (uintptr, error) { mockRspI, ok := d.ReportDataRsp[hex.EncodeToString(req.ReportData[:])] if !ok { return 0, fmt.Errorf("test error: no response for %v", req.ReportData) } mockRsp, ok := mockRspI.(*GetReportResponse) if !ok { return 0, fmt.Errorf("test error: incorrect response type %v", mockRspI) } esResult := uintptr(mockRsp.EsResult) if mockRsp.FwErr != 0 { *fwErr = uint64(mockRsp.FwErr) return esResult, syscall.Errno(unix.EIO) } report := mockRsp.Resp.Data[:abi.ReportSize] r, s, err := d.Signer.Sign(abi.SignedComponent(report)) if err != nil { return 0, fmt.Errorf("test error: could not sign report: %v", err) } if err := abi.SetSignature(r, s, report); err != nil { return 0, fmt.Errorf("test error: could not set signature: %v", err) } copy(rsp.Data[:], report) return esResult, nil } func (d *Device) getExtReport(req *labi.SnpExtendedReportReq, rsp *labi.SnpReportRespABI, fwErr *uint64) (uintptr, error) { if req.CertsLength == 0 { *fwErr = uint64(abi.GuestRequestInvalidLength) req.CertsLength = uint32(len(d.Certs)) return 0, syscall.Errno(unix.EIO) } ret, err := d.getReport(&req.Data, rsp, fwErr) if err != nil { return ret, err } if req.CertsLength < uint32(len(d.Certs)) { return 0, fmt.Errorf("test failure: cert buffer too small: %d < %d", req.CertsLength, len(d.Certs)) } copy(req.Certs, d.Certs) return ret, nil } // DerivedKeyRequestToString translates a DerivedKeyReqABI into a map key string representation. func DerivedKeyRequestToString(req *labi.SnpDerivedKeyReqABI) string { return fmt.Sprintf("%x %x %x %x %x", req.RootKeySelect, req.GuestFieldSelect, req.Vmpl, req.GuestSVN, req.TCBVersion) } func (d *Device) getDerivedKey(req *labi.SnpDerivedKeyReqABI, rsp *labi.SnpDerivedKeyRespABI, _ *uint64) (uintptr, error) { if len(d.Keys) == 0 { return 0, errors.New("test error: no keys") } key, ok := d.Keys[DerivedKeyRequestToString(req)] if !ok { return 0, fmt.Errorf("test error: unmapped key request %v", req) } copy(rsp.Data[:], key) return 0, nil } // Ioctl mocks commands with pre-specified responses for a finite number of requests. func (d *Device) Ioctl(command uintptr, req any) (uintptr, error) { switch sreq := req.(type) { case *labi.SnpUserGuestRequest: switch command { case labi.IocSnpGetReport: return d.getReport(sreq.ReqData.(*labi.SnpReportReqABI), sreq.RespData.(*labi.SnpReportRespABI), &sreq.FwErr) case labi.IocSnpGetDerivedKey: return d.getDerivedKey(sreq.ReqData.(*labi.SnpDerivedKeyReqABI), sreq.RespData.(*labi.SnpDerivedKeyRespABI), &sreq.FwErr) case labi.IocSnpGetExtendedReport: return d.getExtReport(sreq.ReqData.(*labi.SnpExtendedReportReq), sreq.RespData.(*labi.SnpReportRespABI), &sreq.FwErr) default: return 0, fmt.Errorf("invalid command 0x%x", command) } } return 0, fmt.Errorf("unexpected request: %v", req) } // Product returns the mocked product info or the default. func (d *Device) Product() *spb.SevProduct { if d.SevProduct == nil { return abi.DefaultSevProduct() } return d.SevProduct } // QuoteProvider represents a SEV-SNP backed configfs-tsm with pre-programmed responses to attestations. type QuoteProvider struct { Device *Device } // Product returns the mocked product info or the default. func (p *QuoteProvider) Product() *spb.SevProduct { return p.Device.Product() } // IsSupported returns true func (*QuoteProvider) IsSupported() bool { return true } // GetRawQuote returns the raw report assigned for given reportData. func (p *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { mockRspI, ok := p.Device.ReportDataRsp[hex.EncodeToString(reportData[:])] if !ok { return nil, fmt.Errorf("test error: no response for %v", reportData) } mockRsp, ok := mockRspI.(*GetReportResponse) if !ok { return nil, fmt.Errorf("test error: incorrect response type %v", mockRspI) } if mockRsp.FwErr != 0 { return nil, syscall.Errno(unix.EIO) } report := mockRsp.Resp.Data[:abi.ReportSize] r, s, err := p.Device.Signer.Sign(abi.SignedComponent(report)) if err != nil { return nil, fmt.Errorf("test error: could not sign report: %v", err) } if err := abi.SetSignature(r, s, report); err != nil { return nil, fmt.Errorf("test error: could not set signature: %v", err) } if p.Device.SevProduct == nil { return nil, fmt.Errorf("mock SevProduct must not be nil") } extended, err := abi.ExtendPlatformCertTable(p.Device.Certs, &abi.ExtraPlatformInfo{ Size: abi.ExtraPlatformInfoV0Size, Cpuid1Eax: abi.MaskedCpuid1EaxFromSevProduct(p.Device.SevProduct), }) if err != nil { return nil, err } return append(report, extended...), nil } // GetResponse controls how often (Occurrences) a certain response should be // provided. type GetResponse struct { Occurrences uint Body []byte Error error } // Getter is a mock for HTTPSGetter interface that sequentially // returns the configured responses for the provided URL. Responses are returned // as a queue, i.e., always serving from index 0. type Getter struct { Responses map[string][]GetResponse } // SimpleGetter constructs a static server from url -> body responses. // For more elaborate tests, construct a custom Getter. func SimpleGetter(responses map[string][]byte) *Getter { getter := &Getter{ Responses: make(map[string][]GetResponse), } for key, value := range responses { getter.Responses[key] = []GetResponse{ { Occurrences: ^uint(0), Body: value, Error: nil, }, } } return getter } // Get the next response body and error. The response is also removed, // if it has been requested the configured number of times. func (g *Getter) Get(url string) ([]byte, error) { resp, ok := g.Responses[url] if !ok || len(resp) == 0 { return nil, fmt.Errorf("404: %s", url) } body := resp[0].Body err := resp[0].Error resp[0].Occurrences-- if resp[0].Occurrences == 0 { g.Responses[url] = resp[1:] } return body, err } // Done checks that all configured responses have been consumed, and errors // otherwise. func (g *Getter) Done(t testing.TB) { for key := range g.Responses { if len(g.Responses[key]) != 0 { t.Errorf("Prepared response for '%s' not retrieved.", key) } } } go-sev-guest-0.13.0/testing/test_cases.go000066400000000000000000000506671476044460400203100ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testing import ( "encoding/binary" "encoding/hex" "fmt" "time" "github.com/google/go-sev-guest/abi" labi "github.com/google/go-sev-guest/client/linuxabi" "github.com/google/go-sev-guest/kds" spb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/logger" "google.golang.org/protobuf/encoding/prototext" ) // userZeros defines a ReportData example that is all zeros var userZeros [64]byte // userZeros1 defines a ReportData example that is all zeros except the last byte is 1. var userZeros1 = [64]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} // userZeros11 defines a ReportData example that is all zeros except the last 2 bytes are both 1. var userZeros11 = [64]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1} // userZeros12 defines a ReportData example that is all zeros except the last 2 bytes are 1, 2. var userZeros12 = [64]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2} var userZeros13 = [64]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3} var userZeros14 = [64]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4} // zeroReport is a textproto representing an unsigned report response to UserZeros. // The policy just sets the debug bit and bit 17 to 1, and the signature algo 1 is the encoding for // ECDSA P-384 with SHA-348. Every `bytes` field needs to be the correct length. var zeroReport = ` version: 2 policy: 0xa0000 signature_algo: 1 report_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' family_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' image_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' measurement: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' host_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' id_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' author_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id_ma: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' chip_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` // oneReport is a textproto representing an unsigned report response to UserZeros1. var oneReport = ` version: 2 policy: 0xa0000 signature_algo: 1 report_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' family_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' image_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' measurement: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' host_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' id_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' author_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id_ma: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' chip_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` // vlekReport is a textproto representing an unsigned report response to UserZeros12 and a VLEK. var vlekReport = ` version: 2 policy: 0xa0000 signature_algo: 1 signer_info: 4 report_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02' family_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' image_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' measurement: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' host_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' id_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' author_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' report_id_ma: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' chip_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` // TestReportOptions represents a few configurables for generating fake reports from particular inputs. type TestReportOptions struct { ReportData []byte SignerInfo abi.SignerInfo FMS uint32 // If 0, then treated as 2. Version uint32 } // TestRawReport creates a simple V2 raw attestation report with the given REPORT_DATA. // We can't sign the report with AMD keys, and verification isn't the client's responsibility, so // we keep the signature zeros. // Similarly, we leave the randomly-generated fields zero. func TestRawReport(reportData [64]byte) [labi.SnpReportRespReportSize]byte { return CreateRawReport(&TestReportOptions{ReportData: reportData[:]}) } // TestRawReportV3 creates simple V3 raw attestation report with the given REPORT_DATA and CPUID_1_EAX. // We can't sign the report with AMD keys, and verification isn't the client's responsibility, so // we keep the signature zeros. // Similarly, we leave the randomly-generated fields zero. func TestRawReportV3(reportData [64]byte, fms uint32) [labi.SnpReportRespReportSize]byte { return CreateRawReport(&TestReportOptions{ReportData: reportData[:], FMS: fms, Version: 3}) } // CreateRawReport creates simple raw attestation report with the given configurable data in options. // We can't sign the report with AMD keys, and verification isn't the client's responsibility, so // we keep the signature zeros. // Similarly, we leave the randomly-generated fields zero. func CreateRawReport(opts *TestReportOptions) [labi.SnpReportRespReportSize]byte { var r [labi.SnpReportRespReportSize]byte // Set Version to 2 if option is 0 Version := uint32(2) if opts.Version != 0 { Version = opts.Version } binary.LittleEndian.PutUint32(r[0x00:0x04], Version) binary.LittleEndian.PutUint64(r[0x08:0x10], abi.SnpPolicyToBytes(abi.SnpPolicy{Debug: true})) // Signature algorithm ECC P-384 with SHA-384 is encoded as 1. binary.LittleEndian.PutUint32(r[0x34:0x38], 1) binary.LittleEndian.PutUint32(r[0x48:0x4C], abi.ComposeSignerInfo(opts.SignerInfo)) // Family, model, stepping if opts.Version > 2 { family, model, stepping := abi.FmsFromCpuid1Eax(opts.FMS) r[0x188] = family r[0x189] = model r[0x18A] = stepping } // Place user data in its report location. copy(r[0x50:0x90], opts.ReportData) return r } // DeviceOptions specifies customizations for a fake sev-guest device. type DeviceOptions struct { Keys map[string][]byte Now time.Time Signer *AmdSigner Product *spb.SevProduct } func makeTestCerts(opts *DeviceOptions) ([]byte, *AmdSigner, error) { signer := opts.Signer var productName string if opts.Product != nil { productName = kds.ProductName(opts.Product) } else { productName = GetProductName() } if signer == nil { s, err := DefaultTestOnlyCertChain(productName, opts.Now) if err != nil { return nil, nil, err } signer = s } certs, err := signer.CertTableBytes() if err != nil { return nil, nil, err } return certs, signer, nil } // KeyChoice represents which key is expected to have signed the report. type KeyChoice int const ( // KeyChoiceVcek represents the default choice of the VCEK signing the report. KeyChoiceVcek = iota // KeyChoiceVlek represents the choice of the VLEK signing the report. KeyChoiceVlek ) // TestCase represents a get_report input/output test case. type TestCase struct { Name string Input [64]byte Output [labi.SnpReportRespReportSize]byte OutputProto string FwErr abi.SevFirmwareStatus EsResult labi.EsResult EK KeyChoice WantErr string } // TestCases returns common test cases for get_report. func TestCases() []TestCase { zeroRaw := TestRawReport(userZeros) milanReportV3Raw := TestRawReportV3(userZeros13, 0x00a00f10) genoaReportV3Raw := TestRawReportV3(userZeros14, 0x00a10f10) milanReportV3proto, _ := abi.ReportToProto(milanReportV3Raw[:]) genoaReportV3proto, _ := abi.ReportToProto(genoaReportV3Raw[:]) milanReportV3, _ := prototext.MarshalOptions{Multiline: true}.Marshal(milanReportV3proto) genoaReportV3, _ := prototext.MarshalOptions{Multiline: true}.Marshal(genoaReportV3proto) oneRaw := TestRawReport(userZeros1) vlekRaw := CreateRawReport(&TestReportOptions{ ReportData: userZeros12[:], SignerInfo: abi.SignerInfo{SigningKey: abi.VlekReportSigner}, }) return []TestCase{ { Name: "zeros", Input: userZeros, Output: zeroRaw, OutputProto: zeroReport, }, { Name: "zeros and a 1", Input: userZeros1, Output: oneRaw, OutputProto: oneReport, }, { Name: "zeros, 1, 2 in VLEK", Input: userZeros12, Output: vlekRaw, OutputProto: vlekReport, EK: KeyChoiceVlek, }, { Name: "fw oom", Input: userZeros11, FwErr: abi.ResourceLimit, WantErr: "input/output error", // All firmware errors get translated to EIO in configfs }, { Name: "zeros milan v3", Input: userZeros13, Output: milanReportV3Raw, OutputProto: string(milanReportV3), }, { Name: "zeros genoa v3", Input: userZeros14, Output: genoaReportV3Raw, OutputProto: string(genoaReportV3), }, } } // TcDevice returns a mock device populated from test cases' inputs and expected outputs. func TcDevice(tcs []TestCase, opts *DeviceOptions) (*Device, error) { certs, signer, err := makeTestCerts(opts) if err != nil { return nil, fmt.Errorf("test failure creating certificates: %v", err) } responses := map[string]any{} for _, tc := range tcs { responses[hex.EncodeToString(tc.Input[:])] = &GetReportResponse{ Resp: labi.SnpReportRespABI{Data: tc.Output}, FwErr: tc.FwErr, EsResult: tc.EsResult, } } product := opts.Product if product == nil { logger.Warning("test missing sevproduct") product = abi.DefaultSevProduct() } return &Device{ ReportDataRsp: responses, Certs: certs, Signer: signer, Keys: opts.Keys, SevProduct: product, }, nil } // TcQuoteProvider returns a mock quote provider populated from test cases' inputs and expected outputs. func TcQuoteProvider(tcs []TestCase, opts *DeviceOptions) (*QuoteProvider, error) { d, err := TcDevice(tcs, opts) if err != nil { return nil, err } return &QuoteProvider{Device: d}, nil } go-sev-guest-0.13.0/tools/000077500000000000000000000000001476044460400152715ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/attest/000077500000000000000000000000001476044460400165755ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/attest/README.md000066400000000000000000000060361476044460400200610ustar00rootroot00000000000000# `attest` CLI tool This binary is a thin wrapper around the `client` library to gather attestation reports in either AMD API format or in this module's `sevsnp` protobuf formats. The tool's input is the intended `REPORT_DATA` contents, which is 64 bytes of user-provided data to include in the attestation report. This is typically a nonce. The tool's output is the report in any specified format to either standard out or directly to a file. ## Example ``` $ go run . -inform base64 -in \ SGVsbG8gU0VWLVNOUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== \ -out attestation.bin ``` Or equivalently through stdin and default binary input format: ```shell $ echo \ “SGVsbG8gU0VWLVNOUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==†| \ base64 -d | go run . -out attestation.bin ``` If the host does not provide cached certificates, passing --extended will return empty certificates. It's still recommended to use --extended since the verification logic won't change once the host provides cached certificates. The verification will just not need to download them from the AMD Key Distribution Service (KDS). ## Usage ``` ./attest [options...] ``` ### `-extended` The flag requests that the tool uses the extended guest request to get both the attestation report and the host-provided certificates. If `-outform` is `bin`, then the output is the attestation report immediately followed by the certificate table. ### `-in` This flag provides the `REPORT_DATA` content directly on the command line. The contents will be interpreted by the value of the `-inform` flag. The `auto` inform will default to expecting a hexadecimal string. ### `-infile` A path to a file that contains `REPORT_DATA` contents. May be `-` for standard in. If neither `-in` nor `-infile` are specified, then the default input is standard in. The `auto` inform will default to expecting binary. ### `-inform` The format that input takes. One of * `bin`: for raw binary. Must have the expected number of bytes. * `hex`: for a byte string encoded as a hexadecimal string. Fewer bytes than expected will be zero-filled. * `base64`: for a byte string in base64 encoding. Fewer bytes than expected will be zero-filled. * `auto`: has different meanings whether input is from a file or from a command line argument. + If from an argument, then defaults to expecting a hexadecimal string. Will try base64 if hex decoding fails. + If from a file, then defaults to expecting binary. Default value is `auto`. ### `-outform` The format that output takes. This can be `bin` for AMD's specified structures in binary, `proto` for this module's protobuf message types serialized to bytes, or `textproto` for this module's protobuf message types in human readable text format. Default value is `bin`. ### `-out` Path to output file to write attestation report to. Default is empty, interpreted as stdout. ### `-vmpl` The VMPL at which the attestation report should be collected at. Must be between 0 and 3. Default value is 0. go-sev-guest-0.13.0/tools/attest/attest.go000066400000000000000000000150771476044460400204420ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package main implements a CLI tool for collecting attestation reports. package main import ( "errors" "flag" "fmt" "io" "os" "strconv" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/client" pb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/go-sev-guest/tools/lib/cmdline" "github.com/google/logger" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" ) var ( inform = flag.String("inform", "auto", "The format of the reportData input. One of bin, hex, base64, or auto. "+ "Input forms that are not \"bin\" or \"auto\" with a file input will be zero-padded on the right to fill "+ "the expected byte size. If \"bin\" or \"auto\" from a file, then the size must be exact.") outform = flag.String("outform", "bin", "The format of the output attestation report. "+ "One of \"bin\", \"proto\", \"textproto\". "+ "The bin form is for AMD's specified data structures in binary.") extended = flag.Bool("extended", false, "Get both the attestation report and "+ "the host-provided certificate chain. "+ "If -outform=bin, then the binary appears in that order.") reportDataStr = flag.String("in", "", "A string of 64 bytes REPORT_DATA to include in the output attestation. "+ "Big-endian hex, octal, or binary start with 0x, 0o, or 0b respectively, detected with -inform=auto."+ "Little-endian base64 starts with 64x for auto to detect it, or without if -inform=base64. "+ "Little-endian hex is tried last with auto. Default -inform=auto. It is an error to use -inform=bin1") reportData = cmdline.Bytes("-in", abi.ReportDataSize, reportDataStr) reportDataFile = flag.String("infile", "", "Path to a file containing 64 bytes of REPORT_DATA to include "+ "in the output attestation. Stdin is \"-\". Default -inform=bin.") vmpl = flag.String("vmpl", "default", "The VMPL at which to collect an attestation report") out = flag.String("out", "", "Path to output file to write attestation report to. "+ "If unset, outputs to stdout.") verbose = flag.Bool("v", false, "Enable verbose logging.") vmplInt uint ) func indata() ([]byte, error) { if len(*reportData) == 0 && len(*reportDataFile) == 0 { // Default to stdin *reportDataFile = "-" } if len(*reportData) != 0 && len(*reportDataFile) != 0 { return nil, errors.New("cannot specify both of -in and -infile") } if len(*reportData) != 0 { return []byte(*reportData), nil } if *reportDataFile == "-" { return cmdline.ParseBytes("stdin", abi.ReportDataSize, os.Stdin, *inform, cmdline.Filey) } file, err := os.Open(*reportDataFile) if err != nil { return nil, fmt.Errorf("could not open %q: %v", *reportDataFile, err) } defer file.Close() return cmdline.ParseBytes("stdin", abi.ReportDataSize, file, *inform, cmdline.Filey) } func nonBinOut() func(proto.Message) ([]byte, error) { switch *outform { case "proto": return proto.Marshal case "textproto": return prototext.Marshal // unreachable panic since outform is checked in main default: panic(fmt.Sprintf("unknown -outform: %s", *outform)) } } func outputExtendedReport(data [abi.ReportDataSize]byte, out io.Writer) error { if *outform == "bin" { bin, err := getRaw(data) if err != nil { return err } out.Write(bin) return nil } attestation, err := getProto(data) if err != nil { return err } bytes, err := nonBinOut()(attestation) if err != nil { return err } out.Write(bytes) return nil } func getVmpl() (uint, error) { if *vmpl == "default" { return 0, fmt.Errorf("getVmpl should not be called on \"default\"") } vmplInt, err := strconv.ParseUint(*vmpl, 10, 32) if err != nil { return 0, fmt.Errorf("--vmpl must be a non-negative integer or \"default\"") } return uint(vmplInt), nil } func getRaw(data [abi.ReportDataSize]byte) ([]byte, error) { if *vmpl == "default" { qp, err := client.GetQuoteProvider() if err != nil { return nil, err } return qp.GetRawQuote(data) } qp, err := client.GetLeveledQuoteProvider() if err != nil { return nil, err } return qp.GetRawQuoteAtLevel(data, vmplInt) } func getProto(data [abi.ReportDataSize]byte) (*pb.Attestation, error) { if *vmpl == "default" { qp, err := client.GetQuoteProvider() if err != nil { return nil, err } return client.GetQuoteProto(qp, data) } qp, err := client.GetLeveledQuoteProvider() if err != nil { return nil, err } return client.GetQuoteProtoAtLevel(qp, data, vmplInt) } func outputReport(data [abi.ReportDataSize]byte, out io.Writer) error { if *outform == "bin" { bytes, err := getRaw(data) if err != nil { return err } if len(bytes) > abi.ReportSize { bytes = bytes[:abi.ReportSize] } out.Write(bytes) return nil } attestation, err := getProto(data) if err != nil { return err } bytes, err := nonBinOut()(attestation.Report) if err != nil { return err } out.Write(bytes) return nil } func outWriter() (io.Writer, *os.File, error) { if *out == "" { return os.Stdout, nil, nil } file, err := os.Create(*out) if err != nil { return nil, nil, err } return file, file, nil } func main() { logger.Init("", *verbose, false, os.Stderr) flag.Parse() // Second phase of parsing. cmdline.Parse(*inform) reportData, err := indata() if err != nil { logger.Fatal(err) } if !(*outform == "bin" || *outform == "proto" || *outform == "textproto") { logger.Fatalf("-outform is %s. Expect \"bin\", \"proto\", or \"textproto\"", *outform) } if *vmpl != "default" { vint, err := getVmpl() if err != nil || vint > 3 { logger.Fatalf("--vmpl=%s. Expect 0-3 or \"default\"", *vmpl) } vmplInt = vint } outwriter, filetoclose, err := outWriter() if err != nil { logger.Fatal(err) } defer func() { if filetoclose != nil { filetoclose.Close() } }() var reportData64 [abi.ReportDataSize]byte copy(reportData64[:], reportData) if *extended { if err := outputExtendedReport(reportData64, outwriter); err != nil { logger.Fatal(err) } } else { if err := outputReport(reportData64, outwriter); err != nil { logger.Fatal(err) } } } go-sev-guest-0.13.0/tools/check/000077500000000000000000000000001476044460400163465ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/check/README.md000066400000000000000000000122631476044460400176310ustar00rootroot00000000000000# `check` CLI tool This binary is a thin wrapper around the `verify` and `validate` libraries to check attestation reports against expectations. The tool's input is an AMD SEV-SNP attestation report and associated certificates. The tool's output an error or "Success". ## Usage ``` ./check [options...] ``` ### `-in` This flag provides the path to the attestation file to check. Stdin is "-". ### `-inform` The format that input takes. One of * `bin`: for raw binary. This is the attestation report immediately followed by the certificate table if there is one. * `proto`: A binary serialized `sevsnp.Attestation` message. * `textproto`: The `sevsnp.Attestation` message in textproto format. Default value is `bin`. ### `quiet` If set, doesn't write to stdout. All results are communicated through exit code. ### `config` A path to a serialized `check.Config` protocol buffer message that represents values for each of the following flags. If any flags are additionally provided, they are interpreted to override the respective message field. If the path ends in `.textproto`, the message is deserialized with as the human-readable `prototext` format. ### `guest_policy` The most acceptable policy component-wise in its SEV-SNP API 64-bit number format. "Most acceptable" means the minimum API major.minor version, if debug is allowed, if singlesocket is required, if migrateMA is allowed, if SMT is allowed. ### `report_data` The expected exact `REPORT_DATA` value as a hex-encoded string. Unchecked if empty. Default empty. ### `host_data` The expected exact `HOST_DATA` value as a hex-encoded string. Unchecked if empty. Default empty. ### `family_id` The expected exact `FAMILY_ID` value as a hex-encoded string. Unchecked if empty. Default empty. ### `image_id` The expected exact `IMAGE_ID` value as a hex-encoded string. Unchecked if empty. Default empty. ### `report_id` The expected exact `REPORT_ID` value as a hex-encoded string. Unchecked if empty. Default empty. ### `report_id_ma` The expected exact `REPORT_ID_MA` value as a hex-encoded string. Unchecked if empty. Default empty. ### `measurement` The expected exact `MEASUREMENT` value as a hex-encoded string. Unchecked if empty. Default empty. ### `chip_id` The expected exact `CHIP_ID` value as a hex-encoded string. Unchecked if empty. Default empty. ### `-vmpl` The expected VMPL value. ### `minimum_tcb` The component-wise minimum TCB allowed for both the current, committed, and reported TCB values. Default `0`. ### `minimum_launch_tcb` The component-wise minimum TCB allowed for the launch TCB value. Default `0`. ### `provisional` If true, allows reported values to be greater than or equal to than committed values. Default `false` ### `platform_info` The maximum acceptable `PLATFORM_INFO` field bit-wise. If empty, left unchecked. Default empty. ### `require_author_key` If true, requires the attestation report to have `AUTHOR_KEY_EN` set to 1. Will also check `AUTHOR_KEY_DIGEST` against trusted author arguments. Implies `require_idblock` is true. ### `require_idblock` If true, checks that the `ID_KEY_DIGEST` is trusted, either directly against trusted id key arguments, or if the author key is present and the author key is trusted. ### `min_build` The minimum value allowed for both `CURRENT_BUILD` and `COMMITTED_BUILD`. ### `min_version` A `major.minor` version string that specifies the lexicographically minimum values allowed for `{CURRENT,COMMITTED}_{MAJOR,MINOR}`. ### `trusted_author_keys` A colon-separated list of paths to x.509 certificate files for trusted author keys. Combined with `trusted_author_key_hashes`. ### `trusted_author_key_hashes` A comma-separated list of hex-encoded strings for SHA384 digests of trusted author keys in SEV API format. Combined with `trusted_author_keys`. ### `trusted_id_keys` A colon-separated list of paths to x.509 certificate files for trusted id keys. Combined with `trusted_id_key_hashes`. ### `trusted_id_key_hashes` A comma-separated list of hex-encoded strings for SHA384 digests of trusted id keys in SEV API format. Combined with `trusted_id_keys`. ### `product` The name of the AMD product that produced the attestation report. Default `Milan`. ### `product_key_path` A colon-separated list of paths to CA bundles for the product. The expected format of each file is a ASK certificate followed by ARK certificate both in PEM format. ### `check_crl` Download the root key's certificate revocation list and check if the product signing key (ASK) has been revoked. Default `false`. ### `network` Fetch missing files (certificates or CRL) through the network. Default `true`. ## Examples For these examples, we use the `attest` tool to give clarity on the expected format of the input report. The `attest` tool is not required for `check` to work. ```shell $ echo -n "The best nonce" | ./attest > attestation.bin $ hexnonce=$(echo -n "The best nonce" | xxd -p) $ ./check -in attestation.bin -report_data=${hexnonce} ``` ## Exit code meaning * 0: Success * 1: Failure due to tool misuse * 2: Failure due to invalid signature * 3: Failure due to certificate fetch failure * 4: Failure due to certificate revocation list download failure * 5: Failure due to policy go-sev-guest-0.13.0/tools/check/check.go000066400000000000000000000436531476044460400177650ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package main implements a CLI tool for checking SEV-SNP attestation reports. package main import ( "encoding/hex" "errors" "flag" "fmt" "io" "os" "strconv" "strings" "testing" "time" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" checkpb "github.com/google/go-sev-guest/proto/check" spb "github.com/google/go-sev-guest/proto/sevsnp" test "github.com/google/go-sev-guest/testing" "github.com/google/go-sev-guest/tools/lib/cmdline" "github.com/google/go-sev-guest/tools/lib/report" "github.com/google/go-sev-guest/validate" "github.com/google/go-sev-guest/verify" "github.com/google/go-sev-guest/verify/trust" "github.com/google/logger" "go.uber.org/multierr" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/wrapperspb" ) const ( // This is the default guest_policy value only if -policy is not provided. This prevents // the default value from overwriting the message. defaultGuestPolicy = (1 << 17) defaultMinBuild = 0 defaultMinVersion = "0.0" defaultMinTcb = 0 defaultMinLaunchTcb = 0 defaultCheckCrl = false defaultNetwork = true defaultRequireAuthorKey = false defaultRequireIDBlock = false defaultPermitProvisionalFirmware = false // Exit code 1 - tool usage error. exitTool = 1 // Exit code 2 - the report signature did not verify. exitVerify = 2 // Exit code 3 - problem downloading AMD certificates. exitCerts = 3 // Exit code 4 - problem downloading the AMD CRL. exitCrl = 4 // Exit code 5 - the report did not validate according to policy. exitPolicy = 5 ) var ( infile = flag.String("in", "-", "Path to the attestation report to check. Stdin is \"-\".") inform = flag.String("inform", "bin", "The input format for the attestation report. One of \"bin\", \"proto\", \"textproto\".") configProto = flag.String("config", "", ("A path to a serialized check.Config protobuf. Any individual field flags will" + "overwrite the message's associated field. Default unmarshalled as binary. Paths" + " ending in .textproto will be unmarshalled as prototext.")) quiet = flag.Bool("quiet", false, "If true, writes nothing the stdout or stderr. Success is exit code 0, failure exit code 1.") reportdataS = flag.String("report_data", "", "The expected REPORT_DATA field as a hex string. Must encode 64 bytes. Unchecked if unset.") reportdata = cmdline.Bytes("-report_data", abi.ReportDataSize, reportdataS) hostdataS = flag.String("host_data", "", "The expected HOST_DATA field as a hex string. Must encode 32 bytes. Unchecked if unset.") hostdata = cmdline.Bytes("-host_data", abi.HostDataSize, hostdataS) familyidS = flag.String("family_id", "", "The expected FAMILY_ID field as a hex string. Must encode 16 bytes. Unchecked if unset.") familyid = cmdline.Bytes("-family_id", abi.FamilyIDSize, familyidS) imageidS = flag.String("image_id", "", "The expected IMAGE_ID field as a hex string. Must encode 16 bytes. Unchecked if unset.") imageid = cmdline.Bytes("-image_id", abi.ImageIDSize, imageidS) reportidS = flag.String("report_id", "", "The expected REPORT_ID field as a hex string. Must encode 32 bytes. Unchecked if unset.") reportid = cmdline.Bytes("-report_id", abi.ReportIDSize, reportidS) reportidmaS = flag.String("report_id_ma", "", "The expected REPORT_ID_MA field as a hex string. Must encode 32 bytes. Unchecked if unset.") reportidma = cmdline.Bytes("-report_id_ma", abi.ReportIDMASize, reportidmaS) measurementS = flag.String("measurement", "", "The expected MEASUREMENT field as a hex string. Must encode 48 bytes. Unchecked if unset.") measurement = cmdline.Bytes("-measurement", abi.MeasurementSize, measurementS) chipidS = flag.String("chip_id", "", "The expected CHIP_ID field as a hex string. Must encode 64 bytes. Unchecked if unset.") chipid = cmdline.Bytes("-chip_id", abi.ChipIDSize, chipidS) // Optional Uint64. We don't want 0 to override the policy message, so instead of parsing // as Uint64 up front, we keep the flag a string and parse later if given. mintcb = flag.String("minimum_tcb", "", "The minimum acceptable value for CURRENT_TCB, COMMITTED_TCB, and REPORTED_TCB.") minlaunchtcb = flag.String("minimum_launch_tcb", "", "The minimum acceptable value for LAUNCH_TCB.") guestPolicy = flag.String("guest_policy", "", "The most acceptable SnpPolicy component-wise in its 64-bit format.") // Optional Uint8. Similar to above. minbuild = flag.String("min_build", "", "The 8-bit minimum build number for AMD-SP firmware") // Optional Bool. checkcrl = flag.String("check_crl", "", "Download and check the CRL for revoked certificates.") network = flag.String("network", "", "If true, then permitted to download necessary files for verification.") timeout = flag.Duration("timeout", 2*time.Minute, "Duration to continue to retry failed HTTP requests.") maxRetryDelay = flag.Duration("max_retry_delay", 30*time.Second, "Maximum Duration to wait between HTTP request retries.") requireauthor = flag.String("require_author_key", "", "Require that AUTHOR_KEY_EN is 1.") requireidblock = flag.String("require_idblock", "", "Require that the VM was launch with an ID_BLOCK signed by a trusted id key or author key") provisional = flag.String("provisional", "", "Permit provisional firmware (i.e., committed values may be less than current values).") // Optional nibble. vmpl = flag.String("vmpl", "", "The expected VMPL value of the report [0-3].") platforminfo = flag.String("platform_info", "", "The maximum acceptable PLATFORM_INFO field bit-wise. May be empty or a 64-bit unsigned integer") minversion = flag.String("min_version", "", "Minimum AMD-SP firmware API version (major.minor). Each number must be 8-bit non-negative.") trustedauthors = flag.String("trusted_author_keys", "", "Colon-separated paths to x.509 certificates of trusted author keys") trustedauthorhashes = flag.String("trusted_author_key_hashes", "", "Comma-separated hex-encoded SHA-384 hash values of trusted author keys in AMD public key format") trustedidkeys = flag.String("trusted_id_keys", "", "Colon-separated paths to x.509 certificates of trusted author keys") trustedidkeyhashes = flag.String("trusted_id_key_hashes", "", "Comma-separated hex-encoded SHA-384 hash values of trusted identity keys in AMD public key format") stepping = flag.String("stepping", "", "The machine stepping for the chip that generated the attestation report. Default unchecked.") cabundles = flag.String("product_key_path", "", "Colon-separated paths to CA bundles for the AMD product. Must be in PEM format, ASK, then ARK certificates. If unset, uses embedded root certificates.") verbose = flag.Bool("v", false, "Enable verbose logging.") testKdsFile = flag.String("kdsdatabase", "", "Path to a fakekds.Certificates binary cache of AMD KDS") config = &checkpb.Config{ RootOfTrust: &checkpb.RootOfTrust{}, Policy: &checkpb.Policy{}, } product = &spb.SevProduct{} ) func parseHashes(s string) ([][]byte, error) { hexhashes := strings.Split(s, ",") if len(hexhashes) == 1 && hexhashes[0] == "" { return nil, nil } var result [][]byte for _, hexhash := range hexhashes { h, err := hex.DecodeString(strings.TrimSpace(hexhash)) if err != nil { return nil, fmt.Errorf("could not parse hash value as hex-encoded string: %q", hexhash) } result = append(result, h) } return result, nil } func parsePaths(s string) ([]string, error) { paths := strings.Split(s, ":") if len(paths) == 1 && paths[0] == "" { return nil, nil } var result []string for _, path := range paths { p := strings.TrimSpace(path) stat, err := os.Stat(p) if err != nil { return nil, fmt.Errorf("path error for %q: %v", p, err) } if stat.IsDir() { return nil, fmt.Errorf("path is not a file: %q", p) } result = append(result, p) } return result, nil } func getCertBytes(s string) (result [][]byte, err error) { paths, err := parsePaths(s) if err != nil { return nil, err } for _, path := range paths { contents, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("could not read file %q: %v", path, err) } result = append(result, contents) } return result, nil } func parseUint(p string, bits int) (uint64, error) { base := 10 prepped := p if strings.HasPrefix(p, "0x") || strings.HasPrefix(p, "0X") { base = 16 prepped = prepped[2:] } else if strings.HasPrefix(p, "0o") || strings.HasPrefix(p, "0O") { base = 8 prepped = prepped[2:] } else if strings.HasPrefix(p, "0b") || strings.HasPrefix(p, "0B") { base = 2 prepped = prepped[2:] } info64, err := strconv.ParseUint(prepped, base, bits) if err != nil { return 0, fmt.Errorf("%q must be empty or a %d-bit number: %v", p, bits, err) } return info64, nil } func dieWith(err error, exitCode int) { if !*quiet { fmt.Fprintf(os.Stderr, "%v\n", err) } os.Exit(exitCode) } func die(err error) { dieWith(err, exitTool) } func parseConfig(path string) error { if path == "" { return nil } f, err := os.Open(path) if err != nil { return fmt.Errorf("could not open %q: %v", path, err) } defer f.Close() contents, err := io.ReadAll(f) if err != nil { return fmt.Errorf("could not read %q: %v", path, err) } if strings.HasSuffix(path, ".textproto") { err = prototext.Unmarshal(contents, config) } else { err = proto.Unmarshal(contents, config) } if err != nil { return fmt.Errorf("could not deserialize %q: %v", path, err) } // Populate fields that should not be nil if config.RootOfTrust == nil { config.RootOfTrust = &checkpb.RootOfTrust{} } if config.Policy == nil { config.Policy = &checkpb.Policy{} } return nil } func override() bool { return *configProto != "" } func setBool(value *bool, name, flag string, defaultValue bool) error { if flag == "" { if !override() { *value = defaultValue } } else if flag == "true" { *value = true } else if flag == "false" { *value = false } else { return fmt.Errorf("flag -%s=%s invalid. Must be one of unset, \"true\", or \"false\"", name, flag) } return nil } func setUint(value *uint64, bits int, name, flag string, defaultValue uint64) error { if flag == "" { if !override() { *value = defaultValue } } else { u, err := parseUint(flag, bits) if err != nil { return fmt.Errorf("invalid -%s=%s: %v", name, flag, err) } *value = u } return nil } func setUint64(value *uint64, name, flag string, defaultValue uint64) error { return setUint(value, 64, name, flag, defaultValue) } func setUint32(value *uint32, name, flag string, defaultValue uint64) error { v := uint64(*value) if err := setUint(&v, 32, name, flag, defaultValue); err != nil { return err } *value = uint32(v) return nil } func getUIntValue(bits int, name, flag string) (*uint64, error) { if flag == "" { return nil, nil } var u uint64 if err := setUint(&u, bits, name, flag, 0); err != nil { return nil, err } return &u, nil } func setUInt64Value(value **wrapperspb.UInt64Value, name, flag string) error { v, err := getUIntValue(64, name, flag) if v != nil { *value = &wrapperspb.UInt64Value{Value: *v} } return err } func setUInt32Value(value **wrapperspb.UInt32Value, name, flag string) error { v, err := getUIntValue(32, name, flag) if v != nil { *value = &wrapperspb.UInt32Value{Value: uint32(*v)} } return err } func setString(dest *string, _, flag string, defaultValue string) { if flag == "" { // Empty strings are not expected valid values, so override. if !override() || *dest == "" { *dest = defaultValue } } else { *dest = flag } } func populateProduct() error { // The SevProduct can come from either product_name or the combination of product and stepping. if *test.ProductName != "" && (*test.Product != "" || *stepping != "") { return fmt.Errorf("--product_name is mutually exclusive with both --product and --stepping") } // No arguments for product lead to a default value. if *test.ProductName == "" && *test.Product == "" && *stepping == "" { *test.ProductName = test.GetProductName() } var err error if *test.ProductName != "" { product, err = kds.ParseProductName(*test.ProductName, abi.VcekReportSigner) if err != nil { return err } return nil } product, err = kds.ParseProductLine(*test.Product) if err != nil { return fmt.Errorf("--product=%q invalid: %v", *test.Product, err) } return setUInt32Value(&product.MachineStepping, "stepping", *stepping) } func populateRootOfTrust() error { rot := config.RootOfTrust if err := setBool(&rot.CheckCrl, "check_crl", *checkcrl, defaultCheckCrl); err != nil { return err } // The disallow_network field is opposite the network flag since we can't // specify default values in proto3. networkValue := !rot.DisallowNetwork if err := setBool(&networkValue, "network", *network, defaultNetwork); err != nil { return err } rot.DisallowNetwork = !networkValue rot.ProductLine = kds.ProductLine(product) paths, err := parsePaths(*cabundles) if err != nil { return err } if len(paths) > 0 { rot.CabundlePaths = paths } return nil } // Populate fields of the config proto from flags if they override. func populateConfig() error { policy := config.Policy setHashes := func(dest *[][]byte, _, flag string) error { if flag != "" { hashes, err := parseHashes(flag) if err != nil { return err } *dest = hashes } return nil } setCertBytes := func(dest *[][]byte, _, flag string) error { if flag != "" { bytes, err := getCertBytes(flag) if err != nil { return err } *dest = bytes } return nil } setString(&policy.MinimumVersion, "min_version", *minversion, defaultMinVersion) setNonNil := func(dest *[]byte, value []byte) { if value != nil { *dest = value } } setNonNil(&policy.FamilyId, *familyid) setNonNil(&policy.ImageId, *imageid) setNonNil(&policy.ReportData, *reportdata) setNonNil(&policy.Measurement, *measurement) setNonNil(&policy.HostData, *hostdata) setNonNil(&policy.ReportId, *reportid) setNonNil(&policy.ReportIdMa, *reportidma) setNonNil(&policy.ChipId, *chipid) policy.Product = product return multierr.Combine( setUint64(&policy.Policy, "guest_policy", *guestPolicy, defaultGuestPolicy), setUint64(&policy.MinimumTcb, "minimum_tcb", *mintcb, defaultMinTcb), setUint64(&policy.MinimumLaunchTcb, "minimum_launch_tcb", *minlaunchtcb, defaultMinLaunchTcb), setUint32(&policy.MinimumBuild, "min_build", *minbuild, defaultMinBuild), setUInt32Value(&policy.Vmpl, "vmpl", *vmpl), setUInt64Value(&policy.PlatformInfo, "platform_info", *platforminfo), setBool(&policy.RequireAuthorKey, "require_author_key", *requireauthor, defaultRequireAuthorKey), setBool(&policy.RequireIdBlock, "require_idblock", *requireidblock, defaultRequireIDBlock), setBool(&policy.PermitProvisionalFirmware, "permit_provisional_firmware", *provisional, defaultPermitProvisionalFirmware), setHashes(&policy.TrustedAuthorKeyHashes, "trusted_author_key_hashes", *trustedauthorhashes), setHashes(&policy.TrustedIdKeyHashes, "trusted_id_key_hashes", *trustedidkeyhashes), setCertBytes(&policy.TrustedAuthorKeys, "trusted_author_keys", *trustedauthors), setCertBytes(&policy.TrustedIdKeys, "trusted_id_keys", *trustedidkeys)) } func main() { logger.Init("", *verbose, false, os.Stderr) flag.Parse() cmdline.Parse("auto") if err := parseConfig(*configProto); err != nil { die(err) } if err := multierr.Combine(populateProduct(), populateRootOfTrust(), populateConfig()); err != nil { die(err) } if config.RootOfTrust.CheckCrl && config.RootOfTrust.DisallowNetwork { die(errors.New("cannot specify both -check_crl=true and -network=false")) } attestation, err := report.ReadAttestation(*infile, *inform) if err != nil { die(err) } sopts, err := verify.RootOfTrustToOptions(config.RootOfTrust) if err != nil { die(err) } sopts.Product = product sopts.Getter = &trust.RetryHTTPSGetter{ Timeout: *timeout, MaxRetryDelay: *maxRetryDelay, Getter: &trust.SimpleHTTPSGetter{}, } if *testKdsFile != "" { tkds := test.GetKDS(&testing.T{}) kds, ok := tkds.(*test.FakeKDS) if !ok { die(errors.New("--test_kds=amd is mutually exclusive with --kdsdatabase")) } b, err := os.ReadFile(*testKdsFile) if err != nil { die(fmt.Errorf("could not read %q: %v", *testKdsFile, err)) } sopts.Getter = kds if err := proto.Unmarshal(b, kds.Certs); err != nil { die(fmt.Errorf("could not unmarshal KDS database: %v", err)) } } if err := verify.SnpAttestation(attestation, sopts); err != nil { // Make the exit code more helpful when there are network errors // that affected the result. exitCode := exitVerify clarify := func(err error) bool { if err == nil { return false } var certNetworkErr *trust.AttestationRecreationErr var crlNetworkErr *verify.CRLUnavailableErr if errors.As(err, &certNetworkErr) { exitCode = exitCerts return true } else if errors.As(err, &crlNetworkErr) { exitCode = exitCrl return true } return false } if !clarify(err) { clarify(errors.Unwrap(err)) } dieWith(fmt.Errorf("could not verify attestation signature: %v", err), exitCode) } opts, err := validate.PolicyToOptions(config.Policy) if err != nil { die(err) } if err := validate.SnpAttestation(attestation, opts); err != nil { dieWith(fmt.Errorf("error validating attestation: %v", err), exitPolicy) } } go-sev-guest-0.13.0/tools/check/check_test.go000066400000000000000000000340571476044460400210220ustar00rootroot00000000000000package main import ( "bytes" "encoding/hex" "encoding/pem" "fmt" "os" "os/exec" "strconv" "testing" "time" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" checkpb "github.com/google/go-sev-guest/proto/check" kpb "github.com/google/go-sev-guest/proto/fakekds" fakesev "github.com/google/go-sev-guest/testing" "github.com/google/go-sev-guest/verify/testdata" "github.com/google/logger" "go.uber.org/multierr" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/wrapperspb" ) // Returns true if the test should be skipped for the protobuf case since the field // can't be set to the expected value. type setterFn func(p *checkpb.Policy, value string, t *testing.T) bool // Represents a test case that will set a flag or config field to a good or bad value. // We use this data to check that // // - flags alone lead to expected check success or failure, // - a config alone leads to expected check success or failure, // - a config set to a bad value and a flag set to a good value leads // to an expected override and success. // - a config set to a good value and a flag set to a bad value leads // to an expected override and failure. type testCase struct { flag string good string bad []string setter setterFn } var goodPolicy = abi.SnpPolicyToBytes(abi.SnpPolicy{ Debug: true, SMT: true, }) const ( goodChipID = "3ac3fe21e13fb0990eb28a802e3fb6a29483a6b0753590c951bdd3b8e53786184ca39e359669a2b76a1936776b564ea464cdce40c05f63c9b610c5068b006b5d" goodTcb = 4901323769462652930 ) var check string var kdsdatabase string func TestMain(m *testing.M) { if output, err := exec.Command("go", "build", ".").CombinedOutput(); err != nil { die(fmt.Errorf("could not build check tool: %v, %s", err, output)) } check = "./check" // Create a kdsdatabase using testdata for the attestation-producer's VCEK: dbfile, err := os.CreateTemp(".", "kdsdatabase.bin") if err != nil { die(err) } chipid, _ := hex.DecodeString(goodChipID) db := &kpb.Certificates{ChipCerts: []*kpb.Certificates_ChipTCBCerts{ { ChipId: chipid, TcbCerts: map[uint64][]byte{ goodTcb: testdata.VcekBytes, }, }, }} dbbytes, err := proto.Marshal(db) if err != nil { die(err) } n, err := dbfile.Write(dbbytes) if err != nil { die(err) } if n != len(dbbytes) { die(fmt.Errorf("kdsdatabase not fully written")) } kdsdatabase = dbfile.Name() logger.Init("CheckTestLog", false, false, os.Stderr) defer os.Remove(dbfile.Name()) os.Exit(m.Run()) } // Work around the fact that Golang ellipsis unpacking doesn't also pack up // extra singular arguments prior to the unpack. // This means given // // func f(...T) // var a, b T // var c []T // // then // // f(a, b, c...) doesn't typecheck. // // We'd expect the arguments to pack like f([]T{a, b, c...}...) but nope. The array // expression []T{a, b, c...} is also invalid. func withBaseArgs(config string, args ...string) []string { base := []string{ "-in", "../../verify/testdata/attestation.bin", "-kdsdatabase", kdsdatabase, } if config != "" { base = append(base, fmt.Sprintf("-config=%s", config)) } else { base = append(base, fmt.Sprintf("-guest_policy=%d", goodPolicy)) } result := make([]string, len(args)+len(base)) copy(result, base) copy(result[len(base):], args) return result } func setField(p *checkpb.Policy, name string, value any) { r := p.ProtoReflect() ty := r.Descriptor() r.Set(ty.Fields().ByName(protoreflect.Name(name)), protoreflect.ValueOf(value)) } func bytesSetter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { v, err := hex.DecodeString(value) if err != nil { return true } setField(p, name, v) return false } } func stringSetter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { setField(p, name, value) return false } } func boolSetter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { switch value { case "true": setField(p, name, true) case "false": setField(p, name, false) default: return true } return false } } func uint64setter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 64) if err != nil { return true } setField(p, name, u) return false } } func uint32setter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 32) if err != nil { return true } setField(p, name, uint32(u)) return false } } func uint32valueSetter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 32) if err != nil { return true } setField(p, name, wrapperspb.UInt32(uint32(u)).ProtoReflect()) return false } } func uint64valueSetter(name string) setterFn { return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 64) if err != nil { return true } setField(p, name, wrapperspb.UInt64(u).ProtoReflect()) return false } } func testCases() []testCase { return []testCase{ { flag: "report_data", good: "01020304050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", bad: []string{ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100", "not even hex", }, setter: bytesSetter("report_data"), }, { flag: "host_data", good: "0000000000000000000000000000000000000000000000000000000000000000", bad: []string{ "0000000000000000000000000000000000000000000000000000000000000001", // right size "000000000000000000000000000000000000000000000000000000000000000001", // wrong size }, setter: bytesSetter("host_data"), }, { flag: "family_id", good: "00000000000000000000000000000000", bad: []string{"00000000000000000000000000000001"}, setter: bytesSetter("family_id"), }, { flag: "image_id", good: "00000000000000000000000000000000", bad: []string{"00000000000000000000000000000001"}, setter: bytesSetter("image_id"), }, { flag: "report_id", good: "8edc638e1857c555d21f6b11bda3c8b1b5a09dba4852b4c8ee7aa2f16f22cc0a", bad: []string{}, setter: bytesSetter("report_id"), }, { flag: "report_id_ma", good: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", bad: []string{}, setter: bytesSetter("report_id_ma"), }, { flag: "measurement", good: "b07af9620f3b839b47996422ddec6058338951d984e312115131ea82705eaf5b6bdf8a9ece31a5a608eb0cf2e4872b01", bad: []string{}, setter: bytesSetter("measurement"), }, { flag: "chip_id", good: goodChipID, bad: []string{ "3ac3fe21e13fb0990eb28a802e3fb6a29483a6b0753590c951bdd3b8e53786184ca39e359669a2b76a1936776b564ea464cdce40c05f63c9b610c5068b006b5d00", "3ac3fe21e13fb0990eb28a802e3fb6a29483a6b0753590c951bdd3b8e53786184ca39e359669a2b76a1936776b564ea464cdce40c05f63c9b610c5068b006b5e", }, setter: bytesSetter("chip_id"), }, { flag: "minimum_tcb", good: "4901323769462652930", bad: []string{"4901323769462652931"}, setter: uint64setter("minimum_tcb"), }, { flag: "minimum_launch_tcb", good: "4901323769462652930", bad: []string{"4901323769462652931"}, setter: uint64setter("minimum_launch_tcb"), }, { flag: "guest_policy", good: fmt.Sprintf("%d", goodPolicy), bad: []string{"0", "debug"}, setter: uint64setter("policy"), }, { flag: "min_build", good: "0", bad: []string{"257", "90"}, setter: uint32setter("minimum_build"), }, { flag: "min_version", good: "1.49", bad: []string{"0.0.0", "1.50", "0.", ".0"}, setter: stringSetter("minimum_version"), }, { flag: "provisional", good: "true", bad: nil, // The example doesn't have provional firmware setter: boolSetter("permit_provisional_firmware"), }, { flag: "require_author_key", good: "false", bad: []string{"true"}, setter: boolSetter("require_author_key"), }, { flag: "require_idblock", good: "false", // The example doesn't have an IDBLOCK. bad: []string{"true", "yes"}, setter: boolSetter("require_id_block"), }, { flag: "vmpl", good: "0", bad: []string{"1", "4", "wrong", "-1"}, setter: uint32valueSetter("vmpl"), }, { flag: "platform_info", good: "1", bad: []string{"0"}, setter: uint64valueSetter("platform_info"), }, } } // Writes contents to a file that the runner gets a path to and can use, then deletes the file. func withTempFile(contents []byte, t *testing.T, runner func(path string)) { file, err := os.CreateTemp(".", "temp") if err != nil { t.Fatal(err) } defer os.Remove(file.Name()) n, err := file.Write(contents) if err != nil { t.Fatal(err) } if n != len(contents) { t.Fatalf("incomplete write to %q. Wrote %d, want %d", file.Name(), n, len(contents)) } runner(file.Name()) } func withTestConfig(p *checkpb.Policy, t *testing.T, runner func(path string)) { config := &checkpb.Config{Policy: p} out, err := proto.Marshal(config) if err != nil { t.Fatal(err) } withTempFile(out, t, runner) } func TestCheckGoodFlags(t *testing.T) { for _, tc := range testCases() { // Singular good flag t.Run(tc.flag, func(t *testing.T) { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", tc.flag, tc.good), "--product_name=Milan-B0")...) if output, err := cmd.CombinedOutput(); err != nil { t.Errorf("%s failed unexpectedly: %v (%s)", cmd, err, output) } }) } } func TestCheckBadFlags(t *testing.T) { for _, tc := range testCases() { // Singular bad flags for i, bad := range tc.bad { t.Run(fmt.Sprintf("%s[%d]", tc.flag, i+1), func(t *testing.T) { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", tc.flag, bad), "--product_name=Milan-B0")...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s succeeded unexpectedly: %s", cmd, output) } }) } } } func TestCheckGoodFields(t *testing.T) { for _, tc := range testCases() { t.Run(tc.flag, func(t *testing.T) { p := &checkpb.Policy{Policy: goodPolicy} if tc.setter(p, tc.good, t) { t.Fatal("unexpected parse failure") } withTestConfig(p, t, func(path string) { cmd := exec.Command(check, withBaseArgs(path, "--product_name=Milan-B0")...) if output, err := cmd.CombinedOutput(); err != nil { t.Errorf("%s (%v) failed unexpectedly: %v, %s", cmd, p, err, output) } }) }) } } func TestCheckBadFields(t *testing.T) { for _, tc := range testCases() { for i, bad := range tc.bad { t.Run(fmt.Sprintf("%s_bad[%d]", tc.flag, i+1), func(t *testing.T) { p := &checkpb.Policy{Policy: goodPolicy} if tc.setter(p, bad, t) { return } withTestConfig(p, t, func(path string) { cmd := exec.Command(check, withBaseArgs(path, "--product_name=Milan-B0")...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s (%v) succeeded unexpectedly: %s", cmd, p, output) } }) }) } } } func TestCheckGoodFlagOverridesBadField(t *testing.T) { for _, tc := range testCases() { for i, bad := range tc.bad { t.Run(fmt.Sprintf("%s_bad[%d]", tc.flag, i+1), func(t *testing.T) { p := &checkpb.Policy{Policy: goodPolicy} if tc.setter(p, bad, t) { return } withTestConfig(p, t, func(path string) { cmd := exec.Command(check, withBaseArgs(path, fmt.Sprintf("-%s=%s", tc.flag, tc.good), "--product_name=Milan-B0")...) if output, err := cmd.CombinedOutput(); err != nil { t.Errorf("%s (%v) failed unexpectedly: %v, %s", cmd, p, err, output) } }) }) } } } func TestCheckBadFlagOverridesGoodField(t *testing.T) { for _, tc := range testCases() { for i, bad := range tc.bad { t.Run(fmt.Sprintf("%s_bad[%d]", tc.flag, i+1), func(t *testing.T) { p := &checkpb.Policy{Policy: goodPolicy} if tc.setter(p, tc.good, t) { t.Fatal("unexpected parse failure") } withTestConfig(p, t, func(path string) { cmd := exec.Command(check, withBaseArgs(path, fmt.Sprintf("-%s=%s", tc.flag, bad), "--product_name=Milan-B0")...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s (%v) succeeded unexpectedly: %s", cmd, p, output) } }) }) } } } func TestCaBundles(t *testing.T) { signer, err := fakesev.DefaultTestOnlyCertChain(kds.DefaultProductLine(), time.Now()) if err != nil { t.Fatal(err) } fakebundle := &bytes.Buffer{} if err := multierr.Combine( pem.Encode(fakebundle, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ask.Raw}), pem.Encode(fakebundle, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ark.Raw}), ); err != nil { t.Fatal(err) } // -product only has meaning when provided with a custom product_key_path, so test together. goodbad := func(n int, name string) string { if n == 0 { return fmt.Sprintf("good%s", name) } return fmt.Sprintf("bad%s[%d]", name, n+1) } withTempFile(fakebundle.Bytes(), t, func(fakePath string) { products := []string{"Milan", "None"} cabundles := []string{"../../verify/testdata/milan.testcer", fakePath, "doesNotExist"} for i, product := range products { for j, cabundle := range cabundles { t.Run(fmt.Sprintf("%s_%s", goodbad(i, "product"), goodbad(j, "cabundle")), func(t *testing.T) { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-product=%s", product), fmt.Sprintf("-product_key_path=%s", cabundle))...) output, err := cmd.CombinedOutput() // Only the first pair of the cartesian product is good. if i == 0 && j == 0 && err != nil { t.Errorf("%s errored unexpectedly: %v, %s", cmd, err, output) } else if !(i == 0 && j == 0) && err == nil { t.Errorf("%s succeeded unexpectedly: %s", cmd, output) } }) } } }) } go-sev-guest-0.13.0/tools/lib/000077500000000000000000000000001476044460400160375ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/lib/cmdline/000077500000000000000000000000001476044460400174525ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/lib/cmdline/cmdline.go000066400000000000000000000103531476044460400214160ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package cmdline implements command-line utilities for tools. package cmdline import ( "encoding/base64" "encoding/hex" "flag" "fmt" "io" "os" "strings" "unicode/utf8" ) // InputType represents how data is coming in, either via file or string. type InputType int var allFlags []func(inform string) error const ( // Stringy indicates the input is coming from an argument string. // "auto" behavior prefers hexadecimal. Stringy = iota // Filey indicates the input is coming from a file. // "auto" behavior prefers binary. Filey ) func sizedBytes(flag, value string, byteSize int, decode func(string) ([]byte, error)) ([]byte, error) { bytes, err := decode(value) if err != nil { return nil, fmt.Errorf("%s=%s could not be decoded: %v", flag, value, err) } if len(bytes) > byteSize { return nil, fmt.Errorf("%s=%s (%v) is not representable in %d bytes", flag, value, bytes, byteSize) } sized := make([]byte, byteSize) copy(sized, bytes) return sized, nil } func parseBytesFromString(name string, byteSize int, in string, inform string) ([]byte, error) { if !utf8.ValidString(in) { return nil, fmt.Errorf("could not decode %s contents as a UTF-8 string. Try -inform=bin", name) } // Strict forms first. switch inform { case "hex": return sizedBytes(name, in, byteSize, hex.DecodeString) case "base64": return sizedBytes(name, in, byteSize, base64.StdEncoding.DecodeString) case "auto": // "auto" means to try hex encoding first, then base64. if b, err := sizedBytes(name, in, byteSize, hex.DecodeString); err == nil { return b, nil } return sizedBytes(name, in, byteSize, base64.StdEncoding.DecodeString) default: return nil, fmt.Errorf("unknown -inform=%s", inform) } } func isBinForm(inform string, intype InputType) bool { if inform == "bin" { return true } return (intype == Filey && inform == "auto") } // ParseBytes returns the denoted bytes from the reader `in` or an error. func ParseBytes(name string, byteSize int, in io.Reader, inform string, intype InputType) ([]byte, error) { inbytes, err := io.ReadAll(in) if err != nil { return nil, err } // Empty input is treated as an empty array, not a zero-filled byteSize array. // This allows initial values of nil to be distinguishable from 0. if len(inbytes) == 0 { return nil, nil } if isBinForm(inform, intype) { if len(inbytes) != byteSize { return nil, fmt.Errorf("binary input type had %d bytes. Expect exactly %d bytes", len(inbytes), byteSize) } return inbytes, nil } return parseBytesFromString(name, byteSize, strings.TrimSpace(string(inbytes)), inform) } // Bytes is a flag.Func parsing function that translates a string into // a specific byte-width array. // // A byte string can be represented as // * hexadecimal encoded string if -inform=hex or -inform=auto. // * base64 if -inform=base64 or -inform=auto // // Hex string decoding is attempted first with auto. The base64 encoding grammar // intersects with the hex encoding grammar, so -inform=auto can misbehave. func Bytes(name string, byteSize int, in *string) *[]byte { var empty []byte result := &empty allFlags = append(allFlags, func(inform string) error { // No input means to keep the initial value. if *in == "" { return nil } bytes, err := ParseBytes(name, byteSize, strings.NewReader(*in), inform, Stringy) if err != nil { return err } *result = bytes return nil }) return result } // Parse processes all flag data given the input format and the precondition // that all input flags have been parsed. func Parse(inform string) { for _, thunk := range allFlags { if err := thunk(inform); err != nil { fmt.Fprintf(os.Stderr, "%v\n\n", err) flag.Usage() os.Exit(1) } } } go-sev-guest-0.13.0/tools/lib/cmdline/cmdline_test.go000066400000000000000000000125751476044460400224650ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmdline import ( "bytes" "strings" "testing" ) func expect(err error, wantErr string) bool { if err == nil { return wantErr == "" } return wantErr != "" && strings.Contains(err.Error(), wantErr) } func TestParseBytes(t *testing.T) { tests := []struct { name string byteSize int in []byte inform string intype InputType want []byte wantErr string }{ { name: "binary as binary (intype stringy)", byteSize: 4, in: []byte{0x30, 0x31, 0x32, 0x33}, inform: "bin", intype: Stringy, want: []byte{0x30, 0x31, 0x32, 0x33}, }, { name: "binary as binary (intype filey)", byteSize: 4, in: []byte{0x30, 0x31, 0x32, 0x33}, inform: "bin", intype: Filey, want: []byte{0x30, 0x31, 0x32, 0x33}, }, { name: "binary as auto (intype filey)", byteSize: 4, in: []byte{1, 2, 3, 4}, inform: "auto", intype: Filey, want: []byte{1, 2, 3, 4}, }, { name: "binary as auto, not hex-encoded (intype stringy)", byteSize: 4, in: []byte{1, 2, 3, 4}, inform: "auto", intype: Stringy, wantErr: "could not be decoded", }, { name: "hex as hex (intype stringy)", byteSize: 4, in: []byte("0123"), inform: "hex", intype: Stringy, want: []byte{0x01, 0x23, 0, 0}, }, { name: "hex as hex (intype filey)", byteSize: 4, in: []byte("0123"), inform: "hex", intype: Filey, want: []byte{0x01, 0x23, 0, 0}, }, { name: "base64 as base64 (intype stringy)", byteSize: 4, in: []byte("MTIzNA=="), // echo -n "1234" | base64 inform: "base64", intype: Stringy, want: []byte{0x31, 0x32, 0x33, 0x34}, // ASCII codes }, { name: "base64 as base64 (intype filey)", byteSize: 4, in: []byte("MTIzNA=="), // echo -n "1234" | base64 inform: "base64", intype: Filey, want: []byte{0x31, 0x32, 0x33, 0x34}, // ASCII codes }, { name: "base64 as auto does not work with non-hex", byteSize: 4, in: []byte("MTIzNA=="), // echo -n "1234" | base64 inform: "auto", intype: Filey, wantErr: "binary input type had 8 bytes. Expect exactly 4 bytes", }, { name: "hexy base64 as base64", byteSize: 4, in: []byte("1234"), inform: "base64", intype: Stringy, want: []byte{0xd7, 0x6d, 0xf8, 0}, }, { name: "hex auto (intype stringy)", byteSize: 4, in: []byte("1234"), inform: "auto", intype: Stringy, want: []byte{0x12, 0x34, 0, 0}, }, { name: "hex auto (intype filey)", byteSize: 4, in: []byte("1234"), inform: "auto", intype: Filey, want: []byte{0x31, 0x32, 0x33, 0x34}, // ASCII codes }, { name: "non-exact binary", byteSize: 4, in: []byte{2}, inform: "bin", intype: Filey, wantErr: "Expect exactly 4 bytes", }, { name: "chonky hexstring", byteSize: 4, in: []byte("0102030405"), inform: "hex", intype: Filey, wantErr: "test_input=0102030405 ([1 2 3 4 5]) is not representable in 4 bytes", }, { name: "\ufffd", byteSize: 4, // Example from https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt in: []byte{0xf0, 0x80, 0x80, 0x80}, inform: "hex", intype: Stringy, wantErr: "could not decode test_input contents as a UTF-8 string", }, { name: "bad inform", byteSize: 4, in: []byte{0}, inform: "wonk", intype: Filey, wantErr: "unknown -inform=wonk", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { in := bytes.NewReader(tc.in) got, err := ParseBytes("test_input", tc.byteSize, in, tc.inform, tc.intype) if !expect(err, tc.wantErr) { t.Errorf("ParseBytes(%s, %d, %q, %q, %v) errored unexpectedly. Got %v. Want %v", tc.name, tc.byteSize, tc.in, tc.inform, tc.intype, err, tc.wantErr) } if err == nil && !bytes.Equal(got, tc.want) { t.Errorf("ParseBytes(%s, %d, %q, %q, %v) = %v. Want %v", tc.name, tc.byteSize, tc.in, tc.inform, tc.intype, got, tc.want) } }) } } func TestBytes(t *testing.T) { tests := []*struct { name string in string byteSize int want []byte }{ { name: "test_input", byteSize: 4, in: "1234", want: []byte{0x12, 0x34, 0, 0}, }, { name: "empty", byteSize: 4, in: "", want: []byte{}, }, } byteArray := make([]*[]byte, len(tests)) for i, tc := range tests { byteArray[i] = Bytes(tc.name, tc.byteSize, &tc.in) } Parse("auto") for i, tc := range tests { t.Run(tc.name, func(t *testing.T) { if !bytes.Equal(*byteArray[i], tc.want) { t.Errorf("Bytes(%s, %d, &%q) = %v. Want %v", tc.name, tc.byteSize, tc.in, *byteArray[i], tc.want) } }) } } go-sev-guest-0.13.0/tools/lib/report/000077500000000000000000000000001476044460400173525ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/lib/report/report.go000066400000000000000000000107601476044460400212200ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package report provides functions for reading and writing attestation reports of various formats. package report import ( "fmt" "io" "os" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" "go.uber.org/multierr" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" spb "github.com/google/go-sev-guest/proto/sevsnp" ) func parseAttestationBytes(b []byte) (*spb.Attestation, error) { // This format is the attestation report in AMD's specified ABI format, immediately // followed by the certificate table bytes. if len(b) < abi.ReportSize { return nil, fmt.Errorf("attestation contents too small (0x%x bytes). Want at least 0x%x bytes", len(b), abi.ReportSize) } reportBytes := b[0:abi.ReportSize] certBytes := b[abi.ReportSize:] report, err := abi.ReportToProto(reportBytes) if err != nil { return nil, fmt.Errorf("could not parse attestation report: %v", err) } certs := new(abi.CertTable) if err := certs.Unmarshal(certBytes); err != nil { return nil, fmt.Errorf("could not parse certificate table: %v", err) } return &spb.Attestation{Report: report, CertificateChain: certs.Proto()}, nil } // ParseAttestation parses an attestation report from a byte slice as a given format. func ParseAttestation(b []byte, inform string) (*spb.Attestation, error) { switch inform { case "bin": // May have empty certificate buffer to be just a report. return parseAttestationBytes(b) case "proto": result := &spb.Attestation{} aerr := proto.Unmarshal(b, result) var rerr error if aerr != nil { result.Report = &spb.Report{} rerr = proto.Unmarshal(b, result.Report) if rerr != nil { return nil, fmt.Errorf("could not parse as proto: %v", multierr.Append(aerr, rerr)) } } return result, nil case "textproto": result := &spb.Attestation{} aerr := prototext.Unmarshal(b, result) var rerr error if aerr != nil { result.Report = &spb.Report{} rerr = prototext.Unmarshal(b, result.Report) if rerr != nil { return nil, fmt.Errorf("could not parse as textproto: %v", multierr.Append(aerr, rerr)) } } return result, nil default: return nil, fmt.Errorf("unknown inform: %q", inform) } } // ReadAttestation reads an attestation report from a file. func ReadAttestation(infile, inform string) (*spb.Attestation, error) { var in io.Reader var f *os.File if infile == "-" { in = os.Stdin } else { file, err := os.Open(infile) if err != nil { return nil, fmt.Errorf("could not open %q: %v", infile, err) } f = file in = file } defer func() { if f != nil { f.Close() } }() contents, err := io.ReadAll(in) if err != nil { return nil, fmt.Errorf("could not read %q: %v", infile, err) } return ParseAttestation(contents, inform) } func asBin(report *spb.Attestation) ([]byte, error) { r, err := abi.ReportToAbiBytes(report.Report) if err != nil { return nil, err } certs := abi.CertsFromProto(report.CertificateChain).Marshal() return append(r, certs...), nil } func tcbBreakdown(tcb uint64) string { parts := kds.DecomposeTCBVersion(kds.TCBVersion(tcb)) return fmt.Sprintf("0x%x:{ucode: %d, snp: %d, tee: %d, bl: %d}", tcb, parts.UcodeSpl, parts.SnpSpl, parts.TeeSpl, parts.BlSpl) } func tcbText(report *spb.Attestation) ([]byte, error) { return []byte(fmt.Sprintf("current_tcb=%s\ncommitted_tcb=%s\nlaunch_tcb=%s\n", tcbBreakdown(report.Report.GetCurrentTcb()), tcbBreakdown(report.Report.GetCommittedTcb()), tcbBreakdown(report.Report.GetLaunchTcb()))), nil } // Transform returns the attestation in the outform marshalled format. func Transform(report *spb.Attestation, outform string) ([]byte, error) { switch outform { case "bin": return asBin(report) case "proto": return proto.Marshal(report) case "textproto": return prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(report) case "tcb": return tcbText(report) default: return nil, fmt.Errorf("unknown outform: %q", outform) } } go-sev-guest-0.13.0/tools/lib/report/report_test.go000066400000000000000000000152271476044460400222620ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package report import ( "bytes" "fmt" "os" "path" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/client" spb "github.com/google/go-sev-guest/proto/sevsnp" test "github.com/google/go-sev-guest/testing" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" ) var qp client.QuoteProvider var mu sync.Once type reports struct { attestation *spb.Attestation bincerts []byte binreport []byte protocerts []byte protoreport []byte textcerts []byte textreport []byte } var input *reports func initDevice() { now := time.Date(2022, time.May, 3, 9, 0, 0, 0, time.UTC) tests := test.TestCases() ones32 := make([]byte, 32) for i := range ones32 { ones32[i] = 1 } opts := &test.DeviceOptions{Now: now, Product: abi.DefaultSevProduct()} tcqp, err := test.TcQuoteProvider(tests, opts) if err != nil { panic(fmt.Sprintf("failed to create test device: %v", err)) } qp = tcqp var zeros [abi.ReportDataSize]byte bincerts, err := qp.GetRawQuote(zeros) if err != nil { panic(fmt.Errorf("mock failed to quote: %v", err)) } if len(bincerts) < abi.ReportSize+abi.CertTableEntrySize { panic("mock failed to return cert table") } binreport := bincerts[:abi.ReportSize] attestation, err := ParseAttestation(bincerts, "bin") if err != nil { panic(fmt.Errorf("marshal failure: %v", err)) } protocerts, err := proto.Marshal(attestation) if err != nil { panic(fmt.Errorf("marshal failure: %v", err)) } protoreport, err := proto.Marshal(attestation.Report) if err != nil { panic(fmt.Errorf("marshal failure: %v", err)) } textcerts, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(attestation) if err != nil { panic(fmt.Errorf("marshal failure: %v", err)) } textreport, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(attestation.Report) if err != nil { panic(fmt.Errorf("marshal failure: %v", err)) } input = &reports{ attestation: attestation, bincerts: bincerts, binreport: binreport, protocerts: protocerts, protoreport: protoreport, textcerts: textcerts, textreport: textreport, } } func TestParseAttestation(t *testing.T) { mu.Do(initDevice) type testcase struct { input []byte inform string } good := []testcase{ {input.bincerts, "bin"}, {input.binreport, "bin"}, {input.protocerts, "proto"}, {input.protoreport, "proto"}, {input.textcerts, "textproto"}, {input.textreport, "textproto"}, } bad := []testcase{ {input.bincerts, "proto"}, {input.textcerts, "bin"}, {input.protoreport, "textproto"}, } for _, tc := range good { if _, err := ParseAttestation(tc.input, tc.inform); err != nil { t.Fatalf("ParseAttestation(_, %q) = _, %v. Expect nil", tc.inform, err) } } for _, tc := range bad { if _, err := ParseAttestation(tc.input, tc.inform); err == nil { t.Fatalf("ParseAttestation(_, %q) = _, nil. Expected an error", tc.inform) } } } func TestReadAttestation(t *testing.T) { mu.Do(initDevice) type testcase struct { input []byte inform string } good := []testcase{ {input.bincerts, "bin"}, {input.binreport, "bin"}, {input.protocerts, "proto"}, {input.protoreport, "proto"}, {input.textcerts, "textproto"}, {input.textreport, "textproto"}, } bad := []testcase{ {input.bincerts, "proto"}, {input.textcerts, "bin"}, {input.protoreport, "textproto"}, } for _, tc := range good { p := path.Join(t.TempDir(), "input") if err := os.WriteFile(p, tc.input, 0644); err != nil { t.Fatalf("Could not write test file %q: %v", p, err) } if _, err := ReadAttestation(p, tc.inform); err != nil { t.Fatalf("ParseAttestation(_, %q) = _, %v. Expect nil", tc.inform, err) } } for _, tc := range bad { p := path.Join(t.TempDir(), "input") if err := os.WriteFile(p, tc.input, 0644); err != nil { t.Fatalf("Could not write test file %q: %v", p, err) } if _, err := ReadAttestation(p, tc.inform); err == nil { t.Fatalf("ReadAttestation(_, %q) = _, nil. Expected an error", tc.inform) } } } func protoAttestationDiff(left, right []byte) string { leftp := &spb.Attestation{} rightp := &spb.Attestation{} if err := proto.Unmarshal(left, leftp); err != nil { return fmt.Sprintf("left parse: %v", err) } if err := proto.Unmarshal(right, rightp); err != nil { return fmt.Sprintf("right parse: %v", err) } return cmp.Diff(leftp, rightp, protocmp.Transform()) } func binAttestationDiff(left, right []byte) string { if diff := cmp.Diff(left[:abi.ReportSize], right[:abi.ReportSize]); diff != "" { return fmt.Sprintf("Report diff: %s", diff) } leftcerts := left[abi.ReportSize:] rightcerts := right[abi.ReportSize:] leftt := new(abi.CertTable) rightt := new(abi.CertTable) if err := leftt.Unmarshal(leftcerts); err != nil { return "bad left" } if err := rightt.Unmarshal(rightcerts); err != nil { return "bad right" } return cmp.Diff(leftt.Proto(), rightt.Proto(), protocmp.Transform()) } func TestTransform(t *testing.T) { mu.Do(initDevice) t.Run("bin", func(t *testing.T) { binout, err := Transform(input.attestation, "bin") if err != nil { t.Fatalf("Transform(_, \"bin\") = _, %v. Expect nil.", err) } if diff := binAttestationDiff(binout, input.bincerts); diff != "" { t.Fatalf("Transform(_, \"bin\") = %v, nil. Expect %v.\nDiff: %s", binout, input.bincerts, diff) } }) t.Run("proto", func(t *testing.T) { protoout, err := Transform(input.attestation, "proto") if err != nil { t.Fatalf("Transform(_, \"proto\") = _, %v. Expect nil.", err) } if diff := protoAttestationDiff(protoout, input.protocerts); diff != "" { t.Fatalf("Transform(_, \"proto\") = %v, nil. Expect %v.\nDiff: %s", protoout, input.protocerts, diff) } }) t.Run("textproto", func(t *testing.T) { textout, err := Transform(input.attestation, "textproto") if err != nil { t.Fatalf("Transform(_, \"textproto\") = _, %v. Expect nil.", err) } if !bytes.Equal(textout, input.textcerts) { t.Fatalf("Transform(_, \"textproto\") = %v, nil. Expect %v.", string(textout), string(input.textcerts)) } }) } go-sev-guest-0.13.0/tools/show/000077500000000000000000000000001476044460400162515ustar00rootroot00000000000000go-sev-guest-0.13.0/tools/show/main.go000066400000000000000000000033551476044460400175320ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // show reads an attestation report and outputs it in a preferred format. package main import ( "flag" "os" "github.com/google/go-sev-guest/tools/lib/report" "github.com/google/logger" ) var ( infile = flag.String("in", "-", "Path to attestation file, or - for stdin.") inform = flag.String("inform", "bin", "Format of the attestation file. "+ "One of bin, proto, textproto") outfile = flag.String("out", "-", "Path to output file, or - for stdout.") outform = flag.String("outform", "textproto", "Format of the output file. "+ "One of bin, proto, textproto, tcb. Tcb is human-readable.") ) func main() { logger.Init("", false, false, os.Stderr) flag.Parse() attestation, err := report.ReadAttestation(*infile, *inform) if err != nil { logger.Fatal(err) } bin, err := report.Transform(attestation, *outform) if err != nil { logger.Fatal(err) } out := os.Stdout if *outfile != "-" { out, err = os.OpenFile(*outfile, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { logger.Fatalf("Could not open %q: %v", *outfile, err) } } if _, err := out.Write(bin); err != nil { logger.Fatalf("Could not write attestation to %q: %v", *outfile, err) } } go-sev-guest-0.13.0/validate/000077500000000000000000000000001476044460400157225ustar00rootroot00000000000000go-sev-guest-0.13.0/validate/validate.go000066400000000000000000000673631476044460400200610ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package validate is for checking attestation report properties other than signature verification. package validate import ( "bytes" "crypto" "crypto/ecdsa" "crypto/x509" "encoding/hex" "errors" "fmt" "strconv" "strings" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" cpb "github.com/google/go-sev-guest/proto/check" spb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/logger" "go.uber.org/multierr" ) // Options represents verification options for an SEV-SNP attestation report. type Options struct { // GuestPolicy is the maximum of acceptable guest policies. GuestPolicy abi.SnpPolicy // MinimumGuestSvn is the minimum guest security version number. MinimumGuestSvn uint32 // ReportData is the expected REPORT_DATA field. Must be nil or 64 bytes long. Not checked if nil. ReportData []byte // HostData is the expected HOST_DATA field. Must be nil or 32 bytes long. Not checked if nil. HostData []byte // ImageID is the expected IMAGE_ID field. Must be nil or 16 bytes long. Not checked if nil. ImageID []byte // FamilyID is the expected FAMILY_ID field. Must be nil or 16 bytes long. Not checked if nil. FamilyID []byte // ReportID is the expected REPORT_ID field. Must be nil or 32 bytes long. Not checked if nil. ReportID []byte // ReportIDMA is the expected REPORT_ID_MA field. Must be nil or 32 bytes long. Not checked if nil. ReportIDMA []byte // Measurement is the expected MEASUREMENT field. Must be nil or 48 bytes long. Not checked if nil. Measurement []byte // ChipID is the expected CHIP_ID field. Must be nil or 64 bytes long. Not checked if nil. ChipID []byte // MinimumBuild is the minimum firmware build version reported in the attestation report. MinimumBuild uint8 // MinimumVersion is the minimum firmware API version reported in the attestation report, // where the MSB is the major number and the LSB is the minor number. MinimumVersion uint16 // MinimumTCB is the component-wise minimum TCB reported in the attestation report. This // does not include the LaunchTCB. MinimumTCB kds.TCBParts // MinimumLaunchTCB is the component-wise minimum for the attestation report LaunchTCB. MinimumLaunchTCB kds.TCBParts // PermitProvisionalFirmware if true, allows the committed TCB, build, and API values to be less // than or equal to the current values. If false, committed and current values must be equal. PermitProvisionalFirmware bool // PlatformInfo is the maximum of acceptable PLATFORM_INFO data. Not checked if nil. PlatformInfo *abi.SnpPlatformInfo // RequireAuthorKey if true, will not validate a report without AUTHOR_KEY_EN equal to 1. // Implies RequireIDBlock is true. RequireAuthorKey bool // VMPL is the expected VMPL value, 0-3. Unchecked if nil. VMPL *int // RequireIDBlock if true, will not validate a report if it does not have an ID_KEY_DIGEST that // is trusted through all keys in TrustedIDKeys or TrustedIDKeyHashes, or any ID key whose hash // was signed by a key in TrustedAuthorKeys or TrustedIDKeyHashes. No signatures are checked, // since presence in the attestation report implies that the AMD firmware successfully verified // the signature at VM launch. If false, ID_KEY_DIGEST and AUTHOR_KEY_DIGEST are not checked. RequireIDBlock bool // Certificates of keys that are permitted to sign ID keys. Any ID key signed by a trusted author // key is implicitly trusted. Not required if TrustedAuthorKeyHashes is provided. TrustedAuthorKeys []*x509.Certificate // TrustedAuthorKeys is an array of SHA-384 hashes of trusted author keys's public key in SEV-SNP // API format. Not required if TrustedAuthorKeys is provided. TrustedAuthorKeyHashes [][]byte // Certificates of keys that are permitted to sign IDBlocks. Not required if TrustedIDKeyHashes is // provided. TrustedIDKeys []*x509.Certificate // TrustedIDKeyHashes is an array of SHA-384 hashes of trusted ID signer keys's public key in // SEV-SNP API format. Not required if TrustedIDKeys is provided. TrustedIDKeyHashes [][]byte // CertTableOptions allows the caller to specify extra validation conditions on non-standard // UUID entries in the certificate table returned by GetExtendedReport. CertTableOptions map[string]*CertEntryOption } // CertEntryKind represents a simple policy kind for cert table entries. If a UUID string key is // present in the CertTableOptions, then the Validate function must not error when given both the // attestation and the blob associated with the UUID. If a UUID is missing, then the kind matters: // should missing entries be considered an error, or an allowed omission? type CertEntryKind int const ( // CertEntryAllowMissing will only error if the key is present in the certificate table and // Validate returns an error. CertEntryAllowMissing = iota // CertEntryRequire will cause an error if the certificate table does not include the key. CertEntryRequire ) // CertEntryOption represents a pluggable validation option for CertTable entries. This allows for // golden measurements (RIMs and the like) to be injected into the guest about various provided // infrastructure. type CertEntryOption struct { Kind CertEntryKind Validate func(attestation *spb.Attestation, blob []byte) error } func lengthCheck(name string, length int, value []byte) error { if value != nil && len(value) != length { return fmt.Errorf("option %q length is %d. Want %d", name, len(value), length) } return nil } func checkOptionsLengths(opts *Options) error { return multierr.Combine( lengthCheck("family_id", abi.FamilyIDSize, opts.FamilyID), lengthCheck("image_id", abi.ImageIDSize, opts.ImageID), lengthCheck("report_data", abi.ReportDataSize, opts.ReportData), lengthCheck("measurement", abi.MeasurementSize, opts.Measurement), lengthCheck("host_data", abi.HostDataSize, opts.HostData), lengthCheck("report_id", abi.ReportIDSize, opts.ReportID), lengthCheck("report_id_ma", abi.ReportIDMASize, opts.ReportIDMA), lengthCheck("chip_id", abi.ChipIDSize, opts.ChipID)) } // Converts "maj.min" to its uint16 representation or errors. func parseVersion(v string) (uint16, error) { parseU8 := func(name, s string) (uint8, error) { n, err := strconv.Atoi(s) if err != nil { return 0, fmt.Errorf("error parsing %s number: %v", name, err) } if n < 0 || n > 255 { return 0, fmt.Errorf("%s is %d, which is not a uint8", name, n) } return uint8(n), nil } parts := strings.Split(v, ".") if len(parts) != 2 { return 0, fmt.Errorf("expect major.minor, got %q", v) } maj, err := parseU8("major", parts[0]) if err != nil { return 0, err } min, err := parseU8("minor", parts[1]) if err != nil { return 0, err } return (uint16(maj) << 8) | uint16(min), nil } // PolicyToOptions returns an Options object that is represented by a Policy message. func PolicyToOptions(policy *cpb.Policy) (*Options, error) { guestPolicy, err := abi.ParseSnpPolicy(policy.GetPolicy()) if err != nil { return nil, err } var platformInfo *abi.SnpPlatformInfo if policy.GetPlatformInfo() != nil { platformInfoValue, err := abi.ParseSnpPlatformInfo(policy.GetPlatformInfo().GetValue()) if err != nil { return nil, err } platformInfo = &platformInfoValue } var vmpl *int if policy.GetVmpl() != nil { vmplUint32 := policy.GetVmpl().GetValue() if vmplUint32 > 3 { return nil, fmt.Errorf("vmpl is %d. Expect 0-3", vmplUint32) } vmplInt := int(vmplUint32) vmpl = &vmplInt } if policy.GetMinimumBuild() > 255 { return nil, fmt.Errorf("minimum_build is %d. Expect 0-255", policy.GetMinimumBuild()) } minVersion := uint16(0) // Allow an empty minimum version to mean "0.0" if policy.GetMinimumVersion() != "" { minVersion, err = parseVersion(policy.GetMinimumVersion()) if err != nil { return nil, fmt.Errorf("invalid minimum_version, %q: %v", policy.GetMinimumVersion(), err) } } for _, authorKeyHash := range policy.GetTrustedAuthorKeyHashes() { if err := lengthCheck("trusted_author_key_hashes", abi.AuthorKeyDigestSize, authorKeyHash); err != nil { return nil, err } } for _, idKeyHash := range policy.GetTrustedIdKeyHashes() { if err := lengthCheck("trusted_id_key_hashes", abi.IDKeyDigestSize, idKeyHash); err != nil { return nil, err } } parseCerts := func(name string, certs [][]byte) (result []*x509.Certificate, _ error) { for _, certBytes := range certs { cert, err := x509.ParseCertificate(certBytes) if err != nil { return nil, fmt.Errorf("could not parse %s key certificate: %v", name, err) } result = append(result, cert) } return result, nil } authorKeys, err := parseCerts("author", policy.GetTrustedAuthorKeys()) if err != nil { return nil, err } idKeys, err := parseCerts("id", policy.GetTrustedIdKeys()) if err != nil { return nil, err } opts := &Options{ MinimumGuestSvn: policy.GetMinimumGuestSvn(), GuestPolicy: guestPolicy, FamilyID: policy.GetFamilyId(), ImageID: policy.GetImageId(), ReportID: policy.GetReportId(), ReportIDMA: policy.GetReportIdMa(), ChipID: policy.GetChipId(), Measurement: policy.GetMeasurement(), HostData: policy.GetHostData(), ReportData: policy.GetReportData(), PlatformInfo: platformInfo, MinimumTCB: kds.DecomposeTCBVersion(kds.TCBVersion(policy.GetMinimumTcb())), MinimumLaunchTCB: kds.DecomposeTCBVersion(kds.TCBVersion(policy.GetMinimumLaunchTcb())), MinimumBuild: uint8(policy.GetMinimumBuild()), MinimumVersion: minVersion, RequireAuthorKey: policy.GetRequireAuthorKey(), RequireIDBlock: policy.GetRequireIdBlock(), PermitProvisionalFirmware: policy.GetPermitProvisionalFirmware(), TrustedAuthorKeys: authorKeys, TrustedAuthorKeyHashes: policy.GetTrustedAuthorKeyHashes(), TrustedIDKeys: idKeys, TrustedIDKeyHashes: policy.GetTrustedIdKeyHashes(), VMPL: vmpl, } if err := checkOptionsLengths(opts); err != nil { return nil, err } return opts, nil } // <0 if p0 < p1. 0 if p0 = p1. >0 if p0 > p1. func compareByteVersions(major0, minor0, major1, minor1 uint8) int64 { version0 := (uint16(major0) << 8) | uint16(minor0) version1 := (uint16(major1) << 8) | uint16(minor1) return int64(version0) - int64(version1) } func comparePolicyVersions(p0 abi.SnpPolicy, p1 abi.SnpPolicy) int64 { return compareByteVersions(p0.ABIMajor, p0.ABIMinor, p1.ABIMajor, p1.ABIMinor) } func validatePolicy(reportPolicy uint64, required abi.SnpPolicy) error { policy, err := abi.ParseSnpPolicy(reportPolicy) if err != nil { return fmt.Errorf("could not parse SNP policy: %v", err) } if comparePolicyVersions(required, policy) > 0 { return fmt.Errorf( "required policy ABI version (%d.%d) is greater than the report's ABI version (%d.%d)", required.ABIMajor, required.ABIMinor, policy.ABIMajor, policy.ABIMinor) } if !required.MigrateMA && policy.MigrateMA { return errors.New("found unauthorized migration agent capability") } if !required.Debug && policy.Debug { return errors.New("found unauthorized debug capability") } if !required.SMT && policy.SMT { return errors.New("found unauthorized symmetric multithreading (SMT) capability") } if required.SingleSocket && !policy.SingleSocket { return errors.New("required single socket restriction not present") } return nil } func validateByteField(option, field string, size int, given, required []byte) error { if len(required) == 0 { return nil } if len(required) != size { return fmt.Errorf("option %s must be nil or %d bytes", option, size) } if !bytes.Equal(required, given) { return fmt.Errorf("report field %s is %s. Expect %s", field, hex.EncodeToString(given), hex.EncodeToString(required)) } return nil } func validateVerbatimFields(report *spb.Report, options *Options) error { return multierr.Combine( validateByteField("ReportData", "REPORT_DATA", abi.ReportDataSize, report.GetReportData(), options.ReportData), validateByteField("HostData", "HOST_DATA", abi.HostDataSize, report.GetHostData(), options.HostData), validateByteField("FamilyID", "FAMILY_ID", abi.FamilyIDSize, report.GetFamilyId(), options.FamilyID), validateByteField("ImageID", "IMAGE_ID", abi.ImageIDSize, report.GetImageId(), options.ImageID), validateByteField("ReportID", "REPORT_ID", abi.ReportIDSize, report.GetReportId(), options.ReportID), validateByteField("ReportIDMA", "REPORT_ID_MA", abi.ReportIDMASize, report.GetReportIdMa(), options.ReportIDMA), validateByteField("Measurement", "MEASUREMENT", abi.MeasurementSize, report.GetMeasurement(), options.Measurement), validateByteField("ChipID", "CHIP_ID", abi.ChipIDSize, report.GetChipId(), options.ChipID), ) } // partDescription combines a TCB decomposition with a short description. It enables concise // comparisons with high quality error messages. type partDescription struct { parts kds.TCBParts desc string } // reportTcbDescriptions is a collection of all TCB kinds that are within or about a report itself. type reportTcbDescriptions struct { // The host operator's reported TCB, which may not be higher than the current TCB. // May be lower than the current TCB, e.g., if the host wants to ensure a lower bound // TCB across multiple machines, and this one is just ahead of the curve with a newer version. reported partDescription // The firmware version of the VM host machine at the time the report was constructed. current partDescription // When a firmware version is installed and also ensured to not get overwritten with a // firmware with a lower TCB than this. committed partDescription // The CURRENT_TCB version of the machine at the time of launch. launch partDescription // The TCB that the VCEK certificate is certified for. Embedded as x509v3 extensions from // AMD's Key Distribution Service (KDS). cert partDescription } func getReportTcbs(report *spb.Report, certTcb kds.TCBVersion) *reportTcbDescriptions { return &reportTcbDescriptions{ reported: partDescription{ parts: kds.DecomposeTCBVersion(kds.TCBVersion(report.GetReportedTcb())), desc: "report's REPORTED_TCB", }, current: partDescription{ parts: kds.DecomposeTCBVersion(kds.TCBVersion(report.GetCurrentTcb())), desc: "report's CURRENT_TCB", }, committed: partDescription{ parts: kds.DecomposeTCBVersion(kds.TCBVersion(report.GetCommittedTcb())), desc: "report's COMMITTED_TCB", }, launch: partDescription{ parts: kds.DecomposeTCBVersion(kds.TCBVersion(report.GetLaunchTcb())), desc: "report's LAUNCH_TCB", }, cert: partDescription{ parts: kds.DecomposeTCBVersion(certTcb), desc: "TCB of the V[CL]EK certificate", }, } } // policyTcbDescriptions is a collection of all TCB kinds that the validation policy specifies. type policyTcbDescriptions struct { // The validator policy's specified minimum TCB for both reported minimum partDescription // The validator policy's sp minLaunch partDescription } func getPolicyTcbs(options *Options) *policyTcbDescriptions { return &policyTcbDescriptions{ minimum: partDescription{ parts: options.MinimumTCB, desc: "policy minimum TCB", }, minLaunch: partDescription{ parts: options.MinimumLaunchTCB, desc: "policy minimum launch TCB", }, } } // tcbNeError return an error if the two TCBs are not equal func tcbNeError(left, right partDescription) error { ltcb, _ := kds.ComposeTCBParts(left.parts) rtcb, _ := kds.ComposeTCBParts(right.parts) if ltcb == rtcb { return nil } return fmt.Errorf("the %s 0x%x does not match the %s 0x%x", left.desc, ltcb, right.desc, rtcb) } // tcbGtError returns an error if wantLower is greater than (in part) wantHigher. It enforces // the property wantLower <= wantHigher. func tcbGtError(wantLower, wantHigher partDescription) error { if kds.TCBPartsLE(wantLower.parts, wantHigher.parts) { return nil } return fmt.Errorf("the %s %+v is lower than the %s %+v in at least one component", wantHigher.desc, wantHigher.parts, wantLower.desc, wantLower.parts) } // validateTcb returns an error if the TCB values present in the report and V[CL]EK certificate do not // obey expected relationships with respect to the given validation policy, or with respect to // internal consistency checks. func validateTcb(report *spb.Report, certTcb kds.TCBVersion, options *Options) error { reportTcbs := getReportTcbs(report, certTcb) policyTcbs := getPolicyTcbs(options) var provisionalErr error if options.PermitProvisionalFirmware { provisionalErr = tcbGtError(reportTcbs.committed, reportTcbs.current) } else { provisionalErr = tcbNeError(reportTcbs.committed, reportTcbs.current) } return multierr.Combine(provisionalErr, tcbGtError(policyTcbs.minLaunch, reportTcbs.launch), // Any change to the TCB means that the V[CL]EK certificate at an earlier TCB is no // longer valid. The host must make sure that the up-to-date certificate is provisioned // and delivered alongside the report that contains the new reported TCB value. // If the certificate's TCB is greater than the report's TCB, then the host has not // provisioned a certificate for the machine's actual state and should also not be // accepted. tcbNeError(reportTcbs.reported, reportTcbs.cert), tcbGtError(reportTcbs.cert, reportTcbs.current), tcbGtError(policyTcbs.minimum, reportTcbs.reported)) // Note: // * by transitivity of <=, if we're here, then minimum <= current // * since cert == reported, reported <= current // Checks that could make sense but don't: // // * tcbGtError(reportTcbs.launch, reportTcbs.reported) // Since LAUNCH_TCB on a single node is CURRENT_TCB, we expect the opposite ordering. // One only needs to pay attention to LAUNCH_TCB if permitting provisional firmware // but not permitting backsliding the firmware when the VM launched at a higher TCB. // We have no strong recommendations on how such a policy should be enforced. // // * tcbGtError(reportTcbs.launch, reportTcbs.committed), // This seems to be a safe assertion, but the VM Absorb guest message from a migration // agent would allow violation of the ordering. The launch tcb may come from node 1, // where current_tcb and committed_tcb are both higher than node 2's current and // committed tcbs, but the two share the same reported tcb due to a fleetwide commitment // to administer all machines to have a least common TCB in the reported tcb field. // // * tcbGt(reportTcbs.committed, reportTcbs.reported), // The committed TCB <= reported TCB only if you want to have a high standard for // what TCB you report on the machine, but it doesn't match up with previous comments // that we think it reasonable for the reported TCB to be the lowest of the bunch. } func validateVersion(report *spb.Report, options *Options) error { if options.MinimumBuild > uint8(report.GetCurrentBuild()) { return fmt.Errorf("firmware build number %d is less than the required minimum %d", report.GetCurrentBuild(), options.MinimumBuild) } if options.MinimumVersion > (uint16(report.GetCurrentMajor()<<8) | uint16(report.GetCurrentMinor())) { return fmt.Errorf("firmware API version (%d.%d) is less than the required minimum (%d.%d)", report.GetCurrentMajor(), report.GetCurrentMinor(), options.MinimumVersion>>8, options.MinimumVersion&0xff) } buildCmp := int(report.GetCommittedBuild()) - int(report.GetCurrentBuild()) versionCmp := compareByteVersions(uint8(report.GetCommittedMajor()), uint8(report.GetCommittedMinor()), uint8(report.GetCurrentMajor()), uint8(report.GetCurrentMinor())) if !options.PermitProvisionalFirmware { if buildCmp != 0 { return fmt.Errorf("committed build number %d does not match the current build number %d", report.GetCommittedBuild(), report.GetCurrentBuild()) } if versionCmp != 0 { return fmt.Errorf("committed API version (%d.%d) does not match the current API version (%d.%d)", report.GetCommittedMajor(), report.GetCommittedMinor(), report.GetCurrentMajor(), report.GetCurrentMinor()) } } else { if buildCmp > 0 { return fmt.Errorf("committed build number %d is greater than the current build number %d", report.GetCommittedBuild(), report.GetCurrentBuild()) } if versionCmp > 0 { return fmt.Errorf("committed API version (%d.%d) is greater than the current API version (%d.%d)", report.GetCommittedMajor(), report.GetCommittedMinor(), report.GetCurrentMinor(), report.GetCurrentMinor()) } } return nil } func allZero(buf []byte) bool { for _, b := range buf { if b != 0 { return false } } return true } func validatePlatformInfo(platformInfo uint64, required *abi.SnpPlatformInfo) error { if required == nil { return nil } reportInfo, err := abi.ParseSnpPlatformInfo(platformInfo) if err != nil { return fmt.Errorf("could not parse SNP platform info %x: %v", platformInfo, err) } if reportInfo.TSMEEnabled && !required.TSMEEnabled { return errors.New("unauthorized platform feature TSME enabled") } if reportInfo.SMTEnabled && !required.SMTEnabled { return errors.New("unauthorized platform feature SMT enabled") } return nil } func addKeyHashesFromCerts(hashes [][]byte, certs []*x509.Certificate) [][]byte { for _, c := range certs { // Only add ECDSA P-384 keys switch key := c.PublicKey.(type) { case *ecdsa.PublicKey: pubkey, err := abi.EcdsaPublicKeyToBytes(key) if err != nil { // Wrong key type. continue } h := crypto.SHA384.New() h.Write(pubkey) hashes = append(hashes, h.Sum(nil)) } } return hashes } func consolidateKeyHashes(options *Options) error { validateHashes := func(hashes [][]byte, size int) error { for _, hash := range hashes { if len(hash) != size { return fmt.Errorf("found hash with length %d. Expect %d", len(hash), size) } } return nil } if err := validateHashes(options.TrustedIDKeyHashes, abi.IDKeyDigestSize); err != nil { return fmt.Errorf("bad hash size in TrustedIDKeyHashes: %v", err) } if err := validateHashes(options.TrustedAuthorKeyHashes, abi.AuthorKeyDigestSize); err != nil { return fmt.Errorf("bad hash size in TrustedAuthorKeyHashes: %v", err) } options.TrustedIDKeyHashes = addKeyHashesFromCerts(options.TrustedIDKeyHashes, options.TrustedIDKeys) options.TrustedAuthorKeyHashes = addKeyHashesFromCerts(options.TrustedAuthorKeyHashes, options.TrustedAuthorKeys) return nil } func validateKeys(report *spb.Report, options *Options) error { info, err := abi.ParseSignerInfo(report.GetSignerInfo()) if err != nil { return err } if options.RequireAuthorKey && !info.AuthorKeyEn { return errors.New("author key missing when required") } // RequireAuthorKey implies RequireIDBlock. idblock := options.RequireAuthorKey || options.RequireIDBlock if !idblock { return nil } if err := consolidateKeyHashes(options); err != nil { return err } bytesContained := func(hashes [][]byte, digest []byte) bool { for _, hash := range hashes { if bytes.Equal(hash, digest) { return true } } return false } authorKeyTrusted := info.AuthorKeyEn && bytesContained(options.TrustedAuthorKeyHashes, report.GetAuthorKeyDigest()) if options.RequireAuthorKey && !authorKeyTrusted { return fmt.Errorf("report author key not trusted: %v", hex.EncodeToString(report.GetAuthorKeyDigest())) } // If the author key isn't required, check if the ID key itself is trusted. if !authorKeyTrusted && !bytesContained(options.TrustedIDKeyHashes, report.GetIdKeyDigest()) { return fmt.Errorf("report ID key not trusted: %s", hex.EncodeToString(report.GetIdKeyDigest())) } return nil } func validateKeyKind(report *spb.Attestation) (*x509.Certificate, error) { if report == nil { return nil, fmt.Errorf("attestation cannot be nil") } if report.GetReport() == nil { return nil, fmt.Errorf("attestation report cannot be nil") } if report.GetCertificateChain() == nil { return nil, fmt.Errorf("attestation certificate chain cannot be nil") } info, err := abi.ParseSignerInfo(report.GetReport().GetSignerInfo()) if err != nil { return nil, err } switch info.SigningKey { case abi.VcekReportSigner: if len(report.GetCertificateChain().VcekCert) != 0 { return x509.ParseCertificate(report.GetCertificateChain().VcekCert) } case abi.VlekReportSigner: if len(report.GetCertificateChain().VlekCert) != 0 { return x509.ParseCertificate(report.GetCertificateChain().VlekCert) } case abi.NoneReportSigner: return nil, nil } return nil, fmt.Errorf("unsupported key kind %v", info.SigningKey) } func certTableOptions(attestation *spb.Attestation, options map[string]*CertEntryOption) error { extras := attestation.GetCertificateChain().GetExtras() for key, opt := range options { if opt.Validate == nil { return fmt.Errorf("invalid argument: option for %s missing Validate function", key) } if err := opt.Validate(attestation, extras[key]); err != nil { if opt.Kind == CertEntryRequire { return err } logger.Warningf("Missing or invalid cert entry for %s", key) } } return nil } // SnpAttestation validates fields of the protobuf representation of an attestation report against // expectations. Does not check the attestation certificates or signature. func SnpAttestation(attestation *spb.Attestation, options *Options) error { endorsementKeyCert, err := validateKeyKind(attestation) if err != nil { return err } report := attestation.GetReport() info, err := abi.ParseSignerInfo(report.GetSignerInfo()) if err != nil { return err } // Get the TCB values of the V[CL]EK exts, err := kds.CertificateExtensions(endorsementKeyCert, info.SigningKey) if err != nil { return fmt.Errorf("could not get %v certificate extensions: %v", info.SigningKey, err) } if report.GetGuestSvn() < options.MinimumGuestSvn { return fmt.Errorf("report's GUEST_SVN %d is less than the required minimum %d", report.GetGuestSvn(), options.MinimumGuestSvn) } if err := multierr.Combine( validatePolicy(report.GetPolicy(), options.GuestPolicy), validateVerbatimFields(report, options), validateTcb(report, exts.TCBVersion, options), validateVersion(report, options), validatePlatformInfo(report.GetPlatformInfo(), options.PlatformInfo), validateKeys(report, options)); err != nil { return err } if options.VMPL != nil && uint32(*options.VMPL) != report.GetVmpl() { return fmt.Errorf("report VMPL %d is not %d", report.GetVmpl(), *options.VMPL) } // MaskChipId might be 1 for the host, so only check if the the CHIP_ID is not all zeros. if info.SigningKey == abi.VcekReportSigner && !allZero(report.GetChipId()) && !bytes.Equal(report.GetChipId(), exts.HWID[:]) { return fmt.Errorf("report field CHIP_ID %s is not the same as the VCEK certificate's HWID %s", hex.EncodeToString(report.GetChipId()), hex.EncodeToString(exts.HWID[:])) } return certTableOptions(attestation, options.CertTableOptions) } // RawSnpAttestation validates fields of a raw attestation report against expectations. Does not // check the attestation certificates or signature. func RawSnpAttestation(report []byte, certTable []byte, options *Options) error { certs := new(abi.CertTable) if err := certs.Unmarshal(certTable); err != nil { return fmt.Errorf("could not unmarshal SNP certificate table: %v", err) } proto, err := abi.ReportToProto(report) if err != nil { return fmt.Errorf("could not parse attestation report: %v", err) } return SnpAttestation(&spb.Attestation{Report: proto, CertificateChain: certs.Proto()}, options) } go-sev-guest-0.13.0/validate/validate_test.go000066400000000000000000000450061476044460400211060ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package validate is for checking attestation report properties other than signature verification. package validate import ( "bytes" _ "embed" "encoding/pem" "errors" "fmt" "strings" "testing" "time" "github.com/google/go-sev-guest/abi" sg "github.com/google/go-sev-guest/client" labi "github.com/google/go-sev-guest/client/linuxabi" "github.com/google/go-sev-guest/kds" test "github.com/google/go-sev-guest/testing" "github.com/google/go-sev-guest/verify" "go.uber.org/multierr" "google.golang.org/protobuf/encoding/prototext" spb "github.com/google/go-sev-guest/proto/sevsnp" ) const ( snpReportVersion = 2 debugPolicy = 0xa0000 ecdsaSigAlgo = 1 ) func TestValidateSnpAttestation(t *testing.T) { mknonce := func(front []byte) [64]byte { var result [64]byte copy(result[:], front) return result } familyID := []byte{0x01, 0x03, 0x03, 0x07, 0x00, 0x0c, 0x00, 0x0d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} imageID := []byte{0x0f, 0x0e, 0x0e, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f} measurement := []byte{0x01, 0x02, 0x03, 0x06, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0} hostData := []byte{0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0b} idKeyDigest := []byte{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee} authorKeyDigest := []byte{0xdd, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa} reportID := []byte{0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} reportIDMA := []byte{0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} chipID := [64]byte{0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06} goodtcb := kds.TCBParts{ BlSpl: 0x1f, TeeSpl: 0x7f, SnpSpl: 0x70, UcodeSpl: 0x92, } type testOptions struct { currentTcb kds.TCBParts reportedTcb kds.TCBParts committedTcb kds.TCBParts launchTcb kds.TCBParts signerInfo abi.SignerInfo currentBuild uint8 currentMajor uint8 currentMinor uint8 committedBuild uint8 committedMajor uint8 committedMinor uint8 } makeReport := func(reportData [64]byte, opts testOptions) [labi.SnpReportRespReportSize]byte { currentTcb, currerr := kds.ComposeTCBParts(opts.currentTcb) reportedTcb, reportederr := kds.ComposeTCBParts(opts.reportedTcb) committedTcb, committederr := kds.ComposeTCBParts(opts.committedTcb) launchTcb, launcherr := kds.ComposeTCBParts(opts.launchTcb) if err := multierr.Combine(currerr, reportederr, committederr, launcherr); err != nil { t.Fatal(err) } reportpb := &spb.Report{ Version: snpReportVersion, Policy: debugPolicy, SignatureAlgo: ecdsaSigAlgo, ReportData: reportData[:], FamilyId: familyID, ImageId: imageID, Measurement: measurement, HostData: hostData, IdKeyDigest: idKeyDigest, AuthorKeyDigest: authorKeyDigest, ReportId: reportID, ReportIdMa: reportIDMA, ChipId: chipID[:], SignerInfo: abi.ComposeSignerInfo(opts.signerInfo), CommittedBuild: uint32(opts.committedBuild), CommittedMajor: uint32(opts.committedMajor), CommittedMinor: uint32(opts.committedMinor), CurrentBuild: uint32(opts.currentBuild), CurrentMajor: uint32(opts.currentMajor), CurrentMinor: uint32(opts.currentMinor), PlatformInfo: 1, CommittedTcb: uint64(committedTcb), CurrentTcb: uint64(currentTcb), LaunchTcb: uint64(launchTcb), ReportedTcb: uint64(reportedTcb), Signature: make([]byte, abi.SignatureSize), } reportRaw, err := abi.ReportToAbiBytes(reportpb) if err != nil { t.Fatal(err) } var result [labi.SnpReportRespReportSize]byte copy(result[:], reportRaw) return result } // Expensive: generate test keys. keys := test.DefaultAmdKeys() now := time.Now() productName := kds.ProductName(abi.DefaultSevProduct()) sign0, err := test.DefaultTestOnlyCertChain(productName, now) if err != nil { t.Fatal(err) } sb := &test.AmdSignerBuilder{ Keys: keys, ProductName: productName, ArkCreationTime: now, AskCreationTime: now, VcekCreationTime: now, VlekCreationTime: now, VcekCustom: test.CertOverride{ Extensions: test.CustomExtensions( goodtcb, chipID[:], "", productName, ), }, VlekCustom: test.CertOverride{ Extensions: test.CustomExtensions( goodtcb, nil, "Cloud Service Provider", productName, ), }, } sign, err := sb.TestOnlyCertChain() if err != nil { t.Fatal(err) } qp0, err := test.TcQuoteProvider(test.TestCases(), &test.DeviceOptions{Now: now, Signer: sign0, Product: abi.DefaultSevProduct()}) if err != nil { t.Fatal(err) } rootBytes := append( pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: sign.Ask.Raw}), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: sign.Ark.Raw})...) opts := &test.DeviceOptions{ Signer: sign, Now: now, } baseOpts := testOptions{ currentTcb: goodtcb, committedTcb: goodtcb, reportedTcb: goodtcb, launchTcb: goodtcb, signerInfo: abi.SignerInfo{AuthorKeyEn: true}, currentBuild: 2, committedBuild: 2, currentMajor: 1, committedMajor: 1, currentMinor: 49, committedMinor: 49, } var nonce0s1 [64]byte nonce0s1[63] = 1 nonce12345 := mknonce([]byte{1, 2, 3, 4, 5}) nonce54321 := mknonce([]byte{5, 4, 3, 2, 1}) nonceb1455 := mknonce([]byte{0xb, 1, 4, 5, 5}) noncecb1455 := mknonce([]byte{0xc, 0xb, 1, 4, 5, 5}) nonce11355 := mknonce([]byte{1, 1, 3, 5, 5}) tcs := []test.TestCase{ { Name: "deep validation", Input: nonce12345, Output: makeReport(nonce12345, baseOpts), }, { Name: "no author key", Input: nonce54321, Output: func() [labi.SnpReportRespReportSize]byte { opts := baseOpts opts.signerInfo = abi.SignerInfo{} return makeReport(nonce54321, opts) }(), }, { Name: "committed build less", // greater is architecturally illegal Input: nonceb1455, Output: func() [labi.SnpReportRespReportSize]byte { opts := baseOpts opts.committedBuild = 1 return makeReport(nonceb1455, opts) }(), }, { Name: "committed tcb less", // greater is architecturally illegal Input: noncecb1455, Output: func() [labi.SnpReportRespReportSize]byte { opts := baseOpts tcb := goodtcb tcb.BlSpl = 0 opts.committedTcb = tcb opts.launchTcb = tcb return makeReport(noncecb1455, opts) }(), }, { Name: "committed version less", // greater is architecturally illegal Input: nonce11355, Output: func() [labi.SnpReportRespReportSize]byte { opts := baseOpts opts.committedMinor = 49 opts.currentMinor = 51 return makeReport(nonce11355, opts) }(), }, } qp, err := test.TcQuoteProvider(tcs, opts) if err != nil { t.Fatal(err) } getter := test.SimpleGetter( map[string][]byte{ "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain": rootBytes, "https://kdsintf.amd.com/vcek/v1/Milan/0a0b0c0d0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506?blSPL=31&teeSPL=127&snpSPL=112&ucodeSPL=146": sign.Vcek.Raw, }, ) attestationFn := func(nonce [64]byte) *spb.Attestation { q, err := sg.GetQuoteProto(qp, nonce) if err != nil { t.Fatal(err) } report := q.Report attestation, err := verify.GetAttestationFromReport(report, &verify.Options{Getter: getter}) if err != nil { t.Fatal(err) } return attestation } attestation12345 := attestationFn(nonce12345) attestation54321 := attestationFn(nonce54321) attestationb1455 := attestationFn(nonceb1455) attestationcb1455 := attestationFn(noncecb1455) attestation11355 := attestationFn(nonce11355) type testCase struct { name string attestation *spb.Attestation opts *Options wantErr string } tests := []testCase{ { name: "just reportData", attestation: func() *spb.Attestation { q, err := sg.GetQuoteProto(qp0, nonce0s1) if err != nil { t.Fatal(err) } report := q.Report return &spb.Attestation{ Report: report, CertificateChain: &spb.CertificateChain{ AskCert: sign0.Ask.Raw, ArkCert: sign0.Ark.Raw, VcekCert: sign0.Vcek.Raw, }, } }(), opts: &Options{ReportData: nonce0s1[:], GuestPolicy: abi.SnpPolicy{Debug: true}}, }, { name: "deep check", attestation: attestation12345, opts: &Options{ ReportData: nonce12345[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, Measurement: measurement, HostData: hostData, ChipID: chipID[:], FamilyID: familyID, ImageID: imageID, RequireAuthorKey: true, RequireIDBlock: true, ReportID: reportID, ReportIDMA: reportIDMA, MinimumBuild: 2, MinimumVersion: uint16((1 << 8) | 49), MinimumTCB: kds.TCBParts{UcodeSpl: 0x44, SnpSpl: 0x05, BlSpl: 0x02}, TrustedAuthorKeyHashes: [][]byte{authorKeyDigest}, }, }, { name: "Minimum TCB checked", attestation: attestation12345, opts: &Options{ ReportData: nonce12345[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, MinimumTCB: kds.TCBParts{UcodeSpl: 0xff, SnpSpl: 0x05, BlSpl: 0x02}, }, wantErr: "the report's REPORTED_TCB {BlSpl:31 TeeSpl:127 Spl4:0 Spl5:0 Spl6:0 Spl7:0 SnpSpl:112 UcodeSpl:146} is lower than the policy minimum TCB {BlSpl:2 TeeSpl:0 Spl4:0 Spl5:0 Spl6:0 Spl7:0 SnpSpl:5 UcodeSpl:255} in at least one component", }, { name: "Minimum build checked", attestation: attestation12345, opts: &Options{ ReportData: nonce12345[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, MinimumBuild: 3, }, wantErr: "firmware build number 2 is less than the required minimum 3", }, { name: "Minimum version checked", attestation: attestation12345, opts: &Options{ ReportData: nonce12345[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, MinimumVersion: 0xff00, }, wantErr: "firmware API version (1.49) is less than the required minimum (255.0)", }, { name: "Author key checked", attestation: attestation54321, opts: &Options{ ReportData: nonce54321[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, RequireAuthorKey: true, }, // Nevermind that author key digest is nonzero in the fake report. // That can't happen on real hardware. wantErr: "author key missing when required", }, { name: "PlatformInfo checked", attestation: attestation54321, opts: &Options{ ReportData: nonce54321[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{}, }, wantErr: "unauthorized platform feature SMT enabled", }, { name: "Requiring IDBlock requires trust", attestation: attestation12345, opts: &Options{ ReportData: nonce12345[:], GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, RequireIDBlock: true, }, wantErr: "report ID key not trusted: ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ee", }, // TODO(dionnaglaze): test varies ways provisional firmware shows up. // {name: "Provisional firmware"}, { name: "accepted provisional by build", attestation: attestationb1455, opts: &Options{ ReportData: nonceb1455[:], GuestPolicy: abi.SnpPolicy{Debug: true}, PermitProvisionalFirmware: true, }, }, { name: "rejected provisional by build", attestation: attestationb1455, opts: &Options{ReportData: nonceb1455[:], GuestPolicy: abi.SnpPolicy{Debug: true}}, wantErr: "committed build number 1 does not match the current build number 2", }, { name: "accepted provisional by tcb", attestation: attestationcb1455, opts: &Options{ ReportData: noncecb1455[:], GuestPolicy: abi.SnpPolicy{Debug: true}, PermitProvisionalFirmware: true, }, }, { name: "rejected provisional by tcb", attestation: attestationcb1455, opts: &Options{ReportData: noncecb1455[:], GuestPolicy: abi.SnpPolicy{Debug: true}}, wantErr: "the report's COMMITTED_TCB 0x9270000000007f00 does not match the report's CURRENT_TCB 0x9270000000007f1f", }, { name: "accepted provisional by version", attestation: attestation11355, opts: &Options{ ReportData: nonce11355[:], GuestPolicy: abi.SnpPolicy{Debug: true}, PermitProvisionalFirmware: true, }, }, { name: "rejected provisional by version", attestation: attestation11355, opts: &Options{ReportData: nonce11355[:], GuestPolicy: abi.SnpPolicy{Debug: true}}, wantErr: "committed API version (1.49) does not match the current API version (1.51)", }, } numVerbatimFields := 8 for i := 0; i < numVerbatimFields; i++ { opts := &Options{ GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, } var name string switch i { case 0: name = "REPORT_DATA" opts.ReportData = make([]byte, abi.ReportDataSize) case 1: name = "HOST_DATA" opts.HostData = make([]byte, abi.HostDataSize) case 2: name = "FAMILY_ID" opts.FamilyID = make([]byte, abi.FamilyIDSize) case 3: name = "IMAGE_ID" opts.ImageID = make([]byte, abi.ImageIDSize) case 4: name = "REPORT_ID" opts.ReportID = make([]byte, abi.ReportIDSize) case 5: name = "REPORT_ID_MA" opts.ReportIDMA = make([]byte, abi.ReportIDMASize) case 6: name = "MEASUREMENT" opts.Measurement = make([]byte, abi.MeasurementSize) case 7: name = "CHIP_ID" opts.ChipID = make([]byte, abi.ChipIDSize) } tests = append(tests, testCase{ name: fmt.Sprintf("Test incorrect %s", name), attestation: attestation12345, opts: opts, wantErr: fmt.Sprintf("report field %s", name), }) } for _, tc := range tests { if err := SnpAttestation(tc.attestation, tc.opts); (err == nil && tc.wantErr != "") || (err != nil && (tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr))) { t.Errorf("%s: SnpAttestation(%v) errored unexpectedly. Got '%v', want '%s'", tc.name, tc.attestation, err, tc.wantErr) } } } func TestCertTableOptions(t *testing.T) { sign0, err := test.DefaultTestOnlyCertChain(test.GetProductName(), time.Now()) if err != nil { t.Fatal(err) } report := &spb.Report{} if err := prototext.Unmarshal([]byte(test.TestCases()[0].OutputProto), report); err != nil { t.Fatalf("could not unmarshal zero report: %v", err) } attestation := &spb.Attestation{ Report: report, CertificateChain: &spb.CertificateChain{ VcekCert: sign0.Vcek.Raw, Extras: map[string][]byte{ "00000000-0000-c0de-0000-000000000000": []byte("findme"), }, }, } if err := SnpAttestation(attestation, &Options{ GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, CertTableOptions: map[string]*CertEntryOption{ "00000000-feee-feee-0000-000000000000": { Kind: CertEntryRequire, Validate: func(_ *spb.Attestation, blob []byte) error { if blob == nil { return fmt.Errorf("local data is required") } return nil }, }, }, }); err == nil || !strings.Contains(err.Error(), "required") { t.Errorf("SnpAttestation(_, &Options{CertTableOptions: require feee-feee}) = %v, want error to contain %s", err, "required") } if err := SnpAttestation(attestation, &Options{ GuestPolicy: abi.SnpPolicy{Debug: true, SMT: true}, PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, CertTableOptions: map[string]*CertEntryOption{ "00000000-0000-c0de-0000-000000000000": {Kind: CertEntryRequire, Validate: func(_ *spb.Attestation, blob []byte) error { want := []byte("findme") if !bytes.Equal(blob, want) { return fmt.Errorf("c0de entry was %v, want %v", blob, want) } return nil }}, "00000000-feee-feee-0000-000000000000": {Kind: CertEntryAllowMissing, Validate: func(*spb.Attestation, []byte) error { return errors.New("don't call me") }}, }, }); err != nil { t.Errorf("SnpAttestation(_, &Options{CertTableOptions: require c0de, allow feee-fee}) = %v, want nil", err) } } go-sev-guest-0.13.0/verify/000077500000000000000000000000001476044460400154355ustar00rootroot00000000000000go-sev-guest-0.13.0/verify/testdata/000077500000000000000000000000001476044460400172465ustar00rootroot00000000000000go-sev-guest-0.13.0/verify/testdata/attestation.bin000066400000000000000000000022401476044460400222750ustar00rootroot00000000000000 D°zùb;ƒ›G™d"Ýì`X3‰QÙ„ãQ1ê‚p^¯[kߊžÎ1¥¦ë òä‡+ŽÜcŽWÅUÒk½£È±µ ºHR´Èîz¢ño"Ì ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿD:Ãþ!á?°™²Š€.?¶¢”ƒ¦°u5ÉQ½Ó¸å7†L£ž5–i¢·j6wkVN¤dÍÎ@À_cɶÅ‹k]D11DOŽ‹Z¸øùiÊO'¶»¦_ªS®rök‰8t¼åÖ-;º»2ÂÉ¥ÒKP¢2™œÈ!æ‰$k •f¶¶ù0¡_4½e ~ôGøvB‹×é Û,É1ìö”˜U]àgo-sev-guest-0.13.0/verify/testdata/milan.testcer000066400000000000000000000107721476044460400217500ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta 1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2 SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0 60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05 gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs +gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18 fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh 3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4 AFZEAwoKCQ== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/testdata/milanvlek.testcer000066400000000000000000000110031476044460400226160ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 0YPk -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta 1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2 SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0 60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05 gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs +gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18 fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh 3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4 AFZEAwoKCQ== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/testdata/testdata.go000066400000000000000000000030001476044460400213770ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package testdata provides embedded binaries of example ABI material. package testdata import _ "embed" // These certificates are committed regardless of its expiration date since we adjust the // CurrentTime to compare against so that the validity with respect to time is always true. // VcekBytes is an example VCEK certificate as issued by the AMD KDS. // //go:embed vcek.testcer var VcekBytes []byte // MilanVcekBytes is the Milan product vcek cert_chain as issued by the AMD KDS. // // Deprecated: Use trust.AskArkMilanVcekBytes // //go:embed milan.testcer var MilanVcekBytes []byte // MilanVlekBytes is the Milan product vlek cert_chain as issued by the AMD KDS. // // Deprecated: Use trust.AskArkMilanVlekBytes // //go:embed milanvlek.testcer var MilanVlekBytes []byte // AttestationBytes is an example attestation report from a VM that was // launched without an ID_BLOCK. // //go:embed attestation.bin var AttestationBytes []byte go-sev-guest-0.13.0/verify/testdata/vcek.testcer000066400000000000000000000025201476044460400215700ustar00rootroot000000000000000‚L0‚û 0F *†H†÷  09 0  `†He¡0 *†H†÷ 0  `†He¢0£0{10U Engineering1 0 UUS10U Santa Clara1 0 U CA10U Advanced Micro Devices10U SEV-Milan0 220924005528Z 290924005528Z0z10U Engineering1 0 UUS10U Santa Clara1 0 U CA10U Advanced Micro Devices10U SEV-VCEK0v0*†HÎ=+"bHìŸ6.íî^¡vY9Ó ÆÀVÈ!šÞqÅ…Òâw<ÞC0² j¸ߎáØ\ò“£…O`ô…,̓ˆ'r«þ‚ÿÌpúŠcû”õ2È@òç¨5`|Ç%¿[ˆOþÉN>ù£‚0‚0 +œx0 +œx Milan-B00 +œx0 +œx0 +œx0 +œx0 +œx0 +œx0 +œx0 +œxD0M +œx@:Ãþ!á?°™²Š€.?¶¢”ƒ¦°u5ÉQ½Ó¸å7†L£ž5–i¢·j6wkVN¤dÍÎ@À_cɶÅ‹k]0F *†H†÷  09 0  `†He¡0 *†H†÷ 0  `†He¢0£‚'8KÄê¼…Mñ«˜åœTØÒÀÓ7þëÖàÆ_¹”æÐK:,64”uªÚ3e£ünjtz+Lèï*ç°;ý› eÖUæJP!KË]³£I©ˆ·c7Ò9\WŽƒàUf?Nqø›1…kšä§ GÈQ»¸•g0æ>/‚âÚÕ@ZÙPÔ¿»7П-œûr϶iiѦGÔ‘hF°UT=›öšÛ?®›G^É0Õ‚ÏS×¹Q§*Ü%M}á$GªÐqˆ e£ÊsJR$}oÙx'õ´ý&GZªàîËMõžj sXº|¿Ž.Á´–ügÂÿ—U|ë˜Át‰]û²´‡Ã8έsùž°¬µJSHÅräÄ"t­! Ò-ºWDó34^Fºÿ«ñow-,QiaäüÀ£qP¾°<Ùoz¹úé©TGÙuWVUè/îÓƒËÇéÜxÓÓs=ßâ%¢ÔdîìÝ´ÈØèeö4[* a ‡çȃ GwÃ'Wp©[¢Y—ö®6ïþ#7jU$rq BÈÍ%Èó!«Oã€n9ÛçxÚu™%˜‹âÄcÔPß&%JaúÓ+ø†¥ [go-sev-guest-0.13.0/verify/trust/000077500000000000000000000000001476044460400166165ustar00rootroot00000000000000go-sev-guest-0.13.0/verify/trust/ask_ark_genoa.pem000066400000000000000000000107721476044460400221140ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 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 /wMz1R1BHg== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/trust/ask_ark_genoa_vlek.pem000066400000000000000000000110031476044460400231210ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 2cko -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 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 /wMz1R1BHg== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/trust/ask_ark_milan.pem000066400000000000000000000107721476044460400221230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta 1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2 SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0 60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05 gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs +gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18 fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh 3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4 AFZEAwoKCQ== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/trust/ask_ark_milan_vlek.pem000066400000000000000000000110031476044460400231300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 0YPk -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta 1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2 SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0 60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05 gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs +gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18 fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh 3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4 AFZEAwoKCQ== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/trust/ask_ark_turin_vcek.pem000066400000000000000000000111041476044460400231620ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 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 zpacMwFusA== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/trust/ask_ark_turin_vlek.pem000066400000000000000000000111201476044460400231710ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 SUxr -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 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 zpacMwFusA== -----END CERTIFICATE----- go-sev-guest-0.13.0/verify/trust/trust.go000066400000000000000000000321631476044460400203330ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package trust defines core trust types and values for attestation verification. package trust import ( "context" "crypto/x509" _ "embed" "encoding/pem" "fmt" "io" "net/http" "os" "strings" "sync" "time" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" "github.com/google/logger" "go.uber.org/multierr" ) var ( // DefaultRootCerts holds AMD's SEV API certificate format for ASK and ARK keys as published here // https://download.amd.com/developer/eula/sev/ask_ark_milan.cert DefaultRootCerts map[string]*AMDRootCerts // AskArkMilanVcekBytes is a CA bundle for Milan. // source: https://kdsintf.amd.com/vcek/v1/Milan/cert_chain //go:embed ask_ark_milan.pem AskArkMilanVcekBytes []byte // AskArkMilanVlekBytes is a CA bundle for VLEK certs on Milan. // source: https://kdsintf.amd.com/vlek/v1/Milan/cert_chain //go:embed ask_ark_milan_vlek.pem AskArkMilanVlekBytes []byte // AskArkGenoaVcekBytes is a CA bundle for Genoa. // source: https://kdsintf.amd.com/vcek/v1/Genoa/cert_chain //go:embed ask_ark_genoa.pem AskArkGenoaVcekBytes []byte // AskArkGenoaVlekBytes is a CA bundle for VLEK certs on Genoa. // source: https://kdsintf.amd.com/vlek/v1/Genoa/cert_chain //go:embed ask_ark_genoa_vlek.pem AskArkGenoaVlekBytes []byte // AskArkTurinVcekBytes is a CA bundle for VCEK certs on Turin. // source: https://kdsintf.amd.com/vcek/v1/Turin/cert_chain //go:embed ask_ark_turin_vcek.pem AskArkTurinVcekBytes []byte // AskArkTurinVlekBytes is a CA bundle for VLEK certs on Turin. // source: https://kdsintf.amd.com/vcek/v1/Turin/cert_chain //go:embed ask_ark_turin_vlek.pem AskArkTurinVlekBytes []byte // A cache of product certificate KDS results per product. prodCacheMu sync.Mutex productLineCertCache map[string]*ProductCerts ) // Communication with AMD suggests repeat requests of the same arguments will // be throttled to once per 10 seconds. const initialDelay = 10 * time.Second // ProductCerts contains the root key and signing key devoted to a given product line. type ProductCerts struct { Ask *x509.Certificate Asvk *x509.Certificate Ark *x509.Certificate } // AMDRootCerts encapsulates the certificates that represent root of trust in AMD. type AMDRootCerts struct { // Product is the expected CPU product line, e.g., Milan, Turin, Genoa. // // Deprecated: Use ProductLine. Product string // Product is the expected CPU product line, e.g., Milan, Turin, Genoa. ProductLine string // ProductCerts contains the root key and signing key devoted to a given product line. ProductCerts *ProductCerts // AskSev is the AMD certificate representation of the AMD signing key that certifies // versioned chip endoresement keys. If present, the information must match AskX509. AskSev *abi.AskCert // ArkSev is the AMD certificate representation of the self-signed AMD root key that // certifies the AMD signing key. If present, the information must match ArkX509. ArkSev *abi.AskCert // Mu protects concurrent accesses to CRL. Mu sync.Mutex // CRL is the certificate revocation list for this AMD product. Populated once, only when a // revocation is checked. CRL *x509.RevocationList } // GetProductLine returns the product line the certificate chain is associated with. func (r *AMDRootCerts) GetProductLine() string { if r.ProductLine != "" { return r.ProductLine } return r.Product } // AMDRootCertsProduct returns a new *AMDRootCerts for a given product line. func AMDRootCertsProduct(productLine string) *AMDRootCerts { return &AMDRootCerts{ Product: productLine, // TODO(Issue#114): Remove, ProductLine: productLine, } } // HTTPSGetter represents the ability to fetch data from the internet from an HTTP URL. // Used particularly for fetching certificates. type HTTPSGetter interface { Get(url string) ([]byte, error) } // AttestationRecreationErr represents a problem with fetching or interpreting associated // certificates for a given attestation report. This is typically due to network unreliability. type AttestationRecreationErr struct { Msg string } func (e *AttestationRecreationErr) Error() string { return e.Msg } // SimpleHTTPSGetter implements the HTTPSGetter interface with http.Get. type SimpleHTTPSGetter struct{} // Get uses http.Get to return the HTTPS response body as a byte array. func (n *SimpleHTTPSGetter) Get(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, err } else if resp.StatusCode >= 300 { return nil, fmt.Errorf("failed to retrieve '%s' status %d", url, resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } resp.Body.Close() return body, nil } // RetryHTTPSGetter is a meta-HTTPS getter that will retry on failure a given number of times. type RetryHTTPSGetter struct { // Timeout is how long to retry before failure. Timeout time.Duration // MaxRetryDelay is the maximum amount of time to wait between retries. MaxRetryDelay time.Duration // Getter is the non-retrying way of getting a URL. Getter HTTPSGetter } // Get fetches the body of the URL, retrying a given amount of times on failure. func (n *RetryHTTPSGetter) Get(url string) ([]byte, error) { delay := initialDelay ctx, cancel := context.WithTimeout(context.Background(), n.Timeout) var returnedError error for { body, err := n.Getter.Get(url) if err == nil { cancel() return body, nil } returnedError = multierr.Append(returnedError, err) delay = delay + delay if delay > n.MaxRetryDelay { delay = n.MaxRetryDelay } select { case <-ctx.Done(): cancel() return nil, multierr.Append(returnedError, fmt.Errorf("timeout")) // context cancelled case <-time.After(delay): // wait to retry } } } // DefaultHTTPSGetter returns the library's default getter implementation. It will // retry slowly due to the AMD KDS's rate limiting. func DefaultHTTPSGetter() HTTPSGetter { return &RetryHTTPSGetter{ Timeout: 2 * time.Minute, MaxRetryDelay: 30 * time.Second, Getter: &SimpleHTTPSGetter{}, } } // Unmarshal populates ASK and ARK certificates from AMD SEV format certificates in data. func (r *AMDRootCerts) Unmarshal(data []byte) error { ask, index, err := abi.ParseAskCert(data) if err != nil { return fmt.Errorf("could not parse intermediate ASK certificate in SEV certificate format: %v", err) } r.AskSev = ask ark, _, err := abi.ParseAskCert(data[index:]) if err != nil { return fmt.Errorf("could not parse ARK certificate in SEV certificate format: %v", err) } r.ArkSev = ark return nil } // ParseCert returns an X.509 Certificate type for a PEM[CERTIFICATE]- or DER-encoded cert. func ParseCert(cert []byte) (*x509.Certificate, error) { raw := cert b, rest := pem.Decode(cert) if b != nil { if len(rest) > 0 || b.Type != "CERTIFICATE" { return nil, fmt.Errorf("bad type %q or trailing bytes (%d). Expected a single certificate when in PEM format", b.Type, len(rest)) } raw = b.Bytes } return x509.ParseCertificate(raw) } // Decode populates the ProductCerts from DER-formatted certificates for both the AS[V]K and the ARK. func (r *ProductCerts) Decode(ask []byte, ark []byte) error { ica, err := ParseCert(ask) if err != nil { return fmt.Errorf("could not parse intermediate certificate: %v", err) } if strings.HasPrefix(ica.Subject.CommonName, "SEV-VLEK") { r.Asvk = ica } else { r.Ask = ica } arkCert, err := ParseCert(ark) if err != nil { logger.Errorf("could not parse ARK certificate: %v", err) } r.Ark = arkCert return nil } // FromKDSCertBytes populates r's AskX509 and ArkX509 certificates from the two PEM-encoded // certificates in data. This is the format the Key Distribution Service (KDS) uses, e.g., // https://kdsintf.amd.com/vcek/v1/Milan/cert_chain func (r *ProductCerts) FromKDSCertBytes(data []byte) error { ask, ark, err := kds.ParseProductCertChain(data) if err != nil { return err } return r.Decode(ask, ark) } // FromKDSCert populates r's AskX509 and ArkX509 certificates from the certificate format AMD's Key // Distribution Service (KDS) uses, e.g., https://kdsintf.amd.com/vcek/v1/Milan/cert_chain func (r *ProductCerts) FromKDSCert(path string) error { certBytes, err := os.ReadFile(path) if err != nil { return err } return r.FromKDSCertBytes(certBytes) } // X509Options returns the AS[V]K and ARK as the only intermediate and root certificates of an x509 // verification options object, or nil if either key's x509 certificate is not present in r. // The choice between ASK and ASVK is determined bey key. func (r *ProductCerts) X509Options(now time.Time, key abi.ReportSigner) *x509.VerifyOptions { if r.Ark == nil { return nil } roots := x509.NewCertPool() roots.AddCert(r.Ark) intermediates := x509.NewCertPool() switch key { case abi.VcekReportSigner: if r.Ask == nil { return nil } intermediates.AddCert(r.Ask) case abi.VlekReportSigner: if r.Asvk == nil { return nil } intermediates.AddCert(r.Asvk) } return &x509.VerifyOptions{Roots: roots, Intermediates: intermediates, CurrentTime: now} } // ClearProductCertCache clears the product certificate cache. This is useful for testing with // multiple roots of trust. func ClearProductCertCache() { prodCacheMu.Lock() productLineCertCache = nil prodCacheMu.Unlock() } // GetProductChain returns the ASK and ARK certificates of the given product line, either from getter // or from a cache of the results from the last successful call. func GetProductChain(productLine string, s abi.ReportSigner, getter HTTPSGetter) (*ProductCerts, error) { if productLineCertCache == nil { prodCacheMu.Lock() productLineCertCache = make(map[string]*ProductCerts) prodCacheMu.Unlock() } result, ok := productLineCertCache[productLine] if !ok { askark, err := getter.Get(kds.ProductCertChainURL(s, productLine)) if err != nil { return nil, &AttestationRecreationErr{ Msg: fmt.Sprintf("could not download ASK and ARK certificates: %v", err), } } ask, ark, err := kds.ParseProductCertChain(askark) if err != nil { // Treat a bad parse as a network error since it's likely due to an incomplete transfer. return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse root cert_chain: %v", err)} } askCert, err := x509.ParseCertificate(ask) if err != nil { return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse ASK cert: %v", err)} } arkCert, err := x509.ParseCertificate(ark) if err != nil { return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse ARK cert: %v", err)} } result = &ProductCerts{Ask: askCert, Ark: arkCert} prodCacheMu.Lock() productLineCertCache[productLine] = result prodCacheMu.Unlock() } return result, nil } // Forward all the ProductCerts operations from the AMDRootCerts struct to follow the // Law of Demeter. // Decode populates the AMDRootCerts from DER-formatted certificates for both the ASK and the ARK. func (r *AMDRootCerts) Decode(ask []byte, ark []byte) error { r.ProductCerts = &ProductCerts{} return r.ProductCerts.Decode(ask, ark) } // FromKDSCertBytes populates r's AskX509 and ArkX509 certificates from the two PEM-encoded // certificates in data. This is the format the Key Distribution Service (KDS) uses, e.g., // https://kdsintf.amd.com/vcek/v1/Milan/cert_chain func (r *AMDRootCerts) FromKDSCertBytes(data []byte) error { r.ProductCerts = &ProductCerts{} return r.ProductCerts.FromKDSCertBytes(data) } // FromKDSCert populates r's AskX509 and ArkX509 certificates from the certificate format AMD's Key // Distribution Service (KDS) uses, e.g., https://kdsintf.amd.com/vcek/v1/Milan/cert_chain func (r *AMDRootCerts) FromKDSCert(path string) error { r.ProductCerts = &ProductCerts{} return r.ProductCerts.FromKDSCert(path) } // X509Options returns the AS[V]K and ARK as the only intermediate and root certificates of an x509 // verification options object, or nil if either key's x509 certificate is not present in r. // Choice between ASK and ASVK is determined by key. func (r *AMDRootCerts) X509Options(now time.Time, key abi.ReportSigner) *x509.VerifyOptions { if r.ProductCerts == nil { return nil } return r.ProductCerts.X509Options(now, key) } // Parse ASK, ARK certificates from the embedded AMD certificate file. func init() { milanCerts := new(AMDRootCerts) milanCerts.FromKDSCertBytes(AskArkMilanVcekBytes) milanCerts.ProductLine = "Milan" genoaCerts := new(AMDRootCerts) genoaCerts.FromKDSCertBytes(AskArkGenoaVcekBytes) genoaCerts.ProductLine = "Genoa" turinCerts := new(AMDRootCerts) turinCerts.ProductLine = "Turin" turinCerts.FromKDSCertBytes(AskArkTurinVcekBytes) DefaultRootCerts = map[string]*AMDRootCerts{ "Milan": milanCerts, "Genoa": genoaCerts, "Turin": turinCerts, } } go-sev-guest-0.13.0/verify/trust/trust_test.go000066400000000000000000000062071476044460400213720ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trust_test import ( "bytes" "errors" "testing" "time" test "github.com/google/go-sev-guest/testing" "github.com/google/go-sev-guest/verify/trust" ) func TestRetryHTTPSGetter(t *testing.T) { testCases := map[string]struct { getter *test.Getter timeout time.Duration maxRetryDelay time.Duration }{ "immediate success": { getter: &test.Getter{ Responses: map[string][]test.GetResponse{ "https://fetch.me": { { Occurrences: 1, Body: []byte("content"), Error: nil, }, }, }, }, timeout: time.Second, maxRetryDelay: time.Millisecond, }, "second success": { getter: &test.Getter{ Responses: map[string][]test.GetResponse{ "https://fetch.me": { { Occurrences: 1, Body: []byte(""), Error: errors.New("fail"), }, { Occurrences: 1, Body: []byte("content"), Error: nil, }, }, }, }, timeout: time.Second, maxRetryDelay: time.Millisecond, }, "third success": { getter: &test.Getter{ Responses: map[string][]test.GetResponse{ "https://fetch.me": { { Occurrences: 2, Body: []byte(""), Error: errors.New("fail"), }, { Occurrences: 1, Body: []byte("content"), Error: nil, }, }, }, }, timeout: time.Second, maxRetryDelay: time.Millisecond, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { r := &trust.RetryHTTPSGetter{ Timeout: tc.timeout, MaxRetryDelay: tc.maxRetryDelay, Getter: tc.getter, } body, err := r.Get("https://fetch.me") if !bytes.Equal(body, []byte("content")) { t.Errorf("expected '%s' but got '%s'", "content", body) } if err != nil { t.Errorf("expected no error, but got %s", err.Error()) } tc.getter.Done(t) }) } } func TestRetryHTTPSGetterAllFail(t *testing.T) { testGetter := &test.Getter{ Responses: map[string][]test.GetResponse{ "https://fetch.me": { { Occurrences: 1, Body: []byte(""), Error: errors.New("fail"), }, }, }, } r := &trust.RetryHTTPSGetter{ Timeout: 1 * time.Millisecond, MaxRetryDelay: 1 * time.Millisecond, Getter: testGetter, } body, err := r.Get("https://fetch.me") if !bytes.Equal(body, []byte("")) { t.Errorf("expected '%s' but got '%s'", "content", body) } if err == nil { t.Errorf("expected error, but got none") } testGetter.Done(t) } go-sev-guest-0.13.0/verify/verify.go000066400000000000000000001004241476044460400172710ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package verify includes logic and embedded AMD keys to check attestation report signatures. package verify import ( "crypto/ecdsa" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "errors" "flag" "fmt" "time" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" cpb "github.com/google/go-sev-guest/proto/check" spb "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/go-sev-guest/verify/trust" "github.com/google/logger" "go.uber.org/multierr" ) const ( askVersion = 1 askKeyUsage = 0x13 arkVersion = 1 arkKeyUsage = 0x0 askX509Version = 3 asvkX509Version = 3 arkX509Version = 3 ) var ( // ErrMissingVlek is returned when attempting to verify a VLEK-signed report that doesn't also // have its VLEK certificate attached. ErrMissingVlek = errors.New("report signed with VLEK, but VLEK certificate is missing") workaroundStepping = flag.Bool("workaround_kds_productname", false, "If true, don't compare "+ "stepping values from the VCEK certificate and the attestation's or options' Product") ) func askVerifiedBy(signee, signer *abi.AskCert, signeeName, signerName string) error { if signee.CertifyingID != signer.KeyID { return fmt.Errorf("%s's certifying ID (%s) is not %s's key ID (%s) ", signeeName, signerName, signee.CertifyingID.String(), signer.KeyID.String()) } // The signatures in the AskCert format cannot be verified. The signed contents are an x509 // certificate with additional metadata that are not reconstructible from the sevcert file. return nil } func askCertPubKey(cert *abi.AskCert) (*rsa.PublicKey, error) { var result rsa.PublicKey result.N = abi.AmdBigInt(cert.Modulus) exponent := abi.AmdBigInt(cert.PubExp) if !exponent.IsInt64() { return nil, fmt.Errorf("AMD certificate public key exponent too large %s", exponent.String()) } result.E = int(exponent.Int64()) return &result, nil } func crossCheckSevX509(sev *abi.AskCert, x *x509.Certificate) error { // The cross-check is only meaningful if there's more than the X.509 certificates to trust. if sev == nil { return nil } // Perform a cross-check between the X.509 and AMD SEV format certificates. switch pub := x.PublicKey.(type) { case *rsa.PublicKey: certPub, err := askCertPubKey(sev) if err != nil { return err } if !pub.Equal(certPub) { return fmt.Errorf("cross-check failed: SEV cert public key (%v) not equal to X.509 public key (%v)", pub, certPub) } default: return fmt.Errorf("product public key not RSA: %v", x.PublicKey) } return nil } // Check the expected metadata as documented in AMD's KDS specification // https://www.amd.com/system/files/TechDocs/57230.pdf func validateAmdLocation(name pkix.Name, role string) error { checkSingletonList := func(l []string, name, names, value string) error { if len(l) != 1 { return fmt.Errorf("%s has %d %s, want 1", role, len(l), names) } if l[0] != value { return fmt.Errorf("%s %s '%s' not expected for AMD. Expected '%s'", role, name, l[0], value) } return nil } if err := checkSingletonList(name.Country, "country", "countries", "US"); err != nil { return err } if err := checkSingletonList(name.Locality, "locality", "localities", "Santa Clara"); err != nil { return err } if err := checkSingletonList(name.Province, "state", "states", "CA"); err != nil { return err } if err := checkSingletonList(name.Organization, "organization", "organizations", "Advanced Micro Devices"); err != nil { return err } return checkSingletonList(name.OrganizationalUnit, "organizational unit", "organizational units", "Engineering") } func validateRootX509(productLine string, x *x509.Certificate, version int, role, cn string) error { // Additionally check that the X.509 cert's public key matches the SEV format cert. if x == nil { return fmt.Errorf("no X.509 certificate for %s", role) } if x.Version != version { return fmt.Errorf("%s certificate version: %d. Expected %d", role, x.Version, version) } if err := validateAmdLocation(x.Issuer, fmt.Sprintf("%s issuer", role)); err != nil { return err } if err := validateAmdLocation(x.Subject, fmt.Sprintf("%s subject", role)); err != nil { return err } // Only check product name if it's specified. if cn != "" && x.Subject.CommonName != cn { return fmt.Errorf("%s common-name is %s. Expected %s", role, x.Subject.CommonName, cn) } return validateCRLlink(x, productLine, role) } // validateAskX509 checks expected metadata about the ASK X.509 certificate. It does not verify the // cryptographic signatures. func validateAskX509(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } cn := intermediateKeyCommonName(r.GetProductLine(), abi.VcekReportSigner) if err := validateRootX509(r.GetProductLine(), r.ProductCerts.Ask, askX509Version, "ASK", cn); err != nil { return err } if r.AskSev != nil { return crossCheckSevX509(r.AskSev, r.ProductCerts.Ask) } return nil } func endorsementKeyCommonName(key abi.ReportSigner) string { return fmt.Sprintf("SEV-%v", key) } func intermediateKeyCommonName(productLine string, key abi.ReportSigner) string { if productLine != "" { switch key { case abi.VcekReportSigner: return fmt.Sprintf("SEV-%s", productLine) case abi.VlekReportSigner: return fmt.Sprintf("SEV-VLEK-%s", productLine) } } return "" } // validateAsvkX509 checks expected metadata about the ASVK X.509 certificate. It does not verify the // cryptographic signatures. func validateAsvkX509(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } cn := intermediateKeyCommonName(r.GetProductLine(), abi.VlekReportSigner) // There is no ASVK SEV ABI key released by AMD to cross-check. return validateRootX509(r.GetProductLine(), r.ProductCerts.Asvk, asvkX509Version, "ASVK", cn) } // ValidateArkX509 checks expected metadata about the ARK X.509 certificate. It does not verify the // cryptographic signatures. func validateArkX509(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } var cn string if r.GetProductLine() != "" { cn = fmt.Sprintf("ARK-%s", r.GetProductLine()) } if err := validateRootX509(r.GetProductLine(), r.ProductCerts.Ark, arkX509Version, "ARK", cn); err != nil { return err } if r.ArkSev != nil { return crossCheckSevX509(r.ArkSev, r.ProductCerts.Ark) } return nil } // Checks some steps of AMD SEV API Appendix B.3 func validateRootSev(subject, issuer *abi.AskCert, version, keyUsage uint32, subjectRole, issuerRole string) error { // Step 1 or 5 if subject.Version != version { return fmt.Errorf("%s AMD cert is version %d, expected %d", subjectRole, subject.Version, version) } // Step 2 or 6 if subject.KeyUsage != keyUsage { return fmt.Errorf("%s certificate KeyUsage is 0x%x, should be 0x%x", subjectRole, subject.KeyUsage, keyUsage) } return askVerifiedBy(subject, issuer, subjectRole, issuerRole) } // validateAskSev checks ASK SEV format certificate validity according to AMD SEV API Appendix B.3 // This covers steps 1, 2, and 5 func validateAskSev(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } return validateRootSev(r.AskSev, r.ArkSev, askVersion, askKeyUsage, "ASK", "ARK") } // ValidateArkSev checks ARK certificate validity according to AMD SEV API Appendix B.3 // This covers steps 5, 6, 9, and 11. func validateArkSev(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } return validateRootSev(r.ArkSev, r.ArkSev, arkVersion, arkKeyUsage, "ARK", "ARK") } // validateX509 will validate the x509 certificates of the ASK, ASVK, and ARK. func validateX509(r *trust.AMDRootCerts, key abi.ReportSigner) error { if err := validateArkX509(r); err != nil { return fmt.Errorf("ARK validation error: %v", err) } if r.ProductCerts.Ask == nil && key == abi.VcekReportSigner { return fmt.Errorf("trusted root must have ASK certificate for VCEK chain") } if r.ProductCerts.Asvk == nil && key == abi.VlekReportSigner { return fmt.Errorf("trusted root must have ASVK certificate for VLEK chain") } if r.ProductCerts.Ask != nil { if err := validateAskX509(r); err != nil { return fmt.Errorf("ASK validation error: %v", err) } } if r.ProductCerts.Asvk != nil { if err := validateAsvkX509(r); err != nil { return fmt.Errorf("ASVK validation error: %v", err) } } return nil } // validateKDSCertSubject checks KDS-specified values of the subject metadata of the AMD certificate. func validateKDSCertSubject(subject pkix.Name, key abi.ReportSigner) error { if err := validateAmdLocation(subject, fmt.Sprintf("%v subject", key)); err != nil { return err } want := endorsementKeyCommonName(key) if subject.CommonName != want { return fmt.Errorf("%s certificate subject common name %v not expected. Expected %s", key, subject.CommonName, want) } return nil } // validateKDSCertIssuer checks KDS-specified values of the issuer metadata of the AMD certificate. func validateKDSCertIssuer(r *trust.AMDRootCerts, issuer pkix.Name, key abi.ReportSigner) error { if err := validateAmdLocation(issuer, fmt.Sprintf("%v issuer", key)); err != nil { return err } cn := intermediateKeyCommonName(r.GetProductLine(), key) if issuer.CommonName != cn { return fmt.Errorf("%s certificate issuer common name %v not expected. Expected %s", key, issuer.CommonName, cn) } return nil } // CRLUnavailableErr represents a problem with fetching the CRL from the network. // This type is special to allow for easy "fail open" semantics for CRL unavailability. See // Adam Langley's write-up on CRLs and network unreliability // https://www.imperialviolet.org/2014/04/19/revchecking.html type CRLUnavailableErr struct { error } // GetCrlAndCheckRoot downloads the given cert's CRL from one of the distribution points and // verifies that the CRL is valid and doesn't revoke an intermediate key. func GetCrlAndCheckRoot(r *trust.AMDRootCerts, opts *Options) (*x509.RevocationList, error) { r.Mu.Lock() defer r.Mu.Unlock() getter := opts.Getter if getter == nil { getter = trust.DefaultHTTPSGetter() } if r.CRL != nil && opts.Now.Before(r.CRL.NextUpdate) { if err := verifyCRL(r); err != nil { return nil, err } return r.CRL, nil } var errs error for _, url := range r.ProductCerts.Ask.CRLDistributionPoints { bytes, err := getter.Get(url) if err != nil { errs = multierr.Append(errs, err) continue } crl, err := x509.ParseRevocationList(bytes) if err != nil { errs = multierr.Append(errs, err) continue } r.CRL = crl if err := verifyCRL(r); err != nil { return nil, err } return r.CRL, nil } return nil, CRLUnavailableErr{multierr.Append(errs, errors.New("could not fetch product CRL"))} } // verifyCRL checks that the VCEK CRL is signed by the ARK. Must be called after r.CRL is set and while // r.Mu is held. func verifyCRL(r *trust.AMDRootCerts) error { if r.CRL == nil { return errors.New("internal error: CRL not set") } if r.ProductCerts.Ark == nil { return errors.New("missing ARK x509 certificate to check CRL validity") } if r.ProductCerts.Ask == nil { return errors.New("missing ASK x509 certificate to check intermediate key validity") } if err := r.CRL.CheckSignatureFrom(r.ProductCerts.Ark); err != nil { return fmt.Errorf("CRL is not signed by ARK: %v", err) } for _, bad := range r.CRL.RevokedCertificates { if r.ProductCerts.Ask.SerialNumber.Cmp(bad.SerialNumber) == 0 { return fmt.Errorf("ASK was revoked at %v", bad.RevocationTime) } // From offline discussions with AMD, we don't expect them to ever explicitly revoke a VCEK // since TCB numbers serve the purpose of superceding previous certificates. } return nil } // VcekNotRevoked will consult the online CRL listed in the VCEK certificate for whether this cert // has been revoked. Returns nil if not revoked, error on any problem. func VcekNotRevoked(r *trust.AMDRootCerts, _ *x509.Certificate, options *Options) error { _, err := GetCrlAndCheckRoot(r, options) return err } // product is expected to be of form "Milan" or "Genoa". // role is expected to be one of "ARK", "ASK", "ASVK". func validateCRLlink(x *x509.Certificate, productLine, role string) error { url := kds.CrlLinkByRole(productLine, role) if len(x.CRLDistributionPoints) != 1 { return fmt.Errorf("%s has %d CRL distribution points, want 1", role, len(x.CRLDistributionPoints)) } if x.CRLDistributionPoints[0] != url { return fmt.Errorf("%s CRL distribution point is '%s', want '%s'", role, x.CRLDistributionPoints[0], url) } return nil } // validateVcekExtensions checks if the certificate extensions match // wellformedness expectations. func validateExtensions(exts *kds.Extensions, key abi.ReportSigner) error { _, err := kds.ParseProductName(exts.ProductName, key) return err } // validateKDSCertificateProductNonspecific returns an error if the given certificate doesn't have // the documented qualities of a V[CL]EK certificate according to Key Distribution Service // documentation: // https://www.amd.com/system/files/TechDocs/57230.pdf // This does not check the certificate revocation list since that requires internet access. // If valid, then returns the V[CL]EK-specific certificate extensions in the VcekExtensions type. func validateKDSCertificateProductNonspecific(cert *x509.Certificate, key abi.ReportSigner) (*kds.Extensions, error) { if cert.Version != 3 { return nil, fmt.Errorf("%v certificate version is %v, expected 3", key, cert.Version) } // Signature algorithm: RSASSA-PSS // Signature hash algorithm sha384 if cert.SignatureAlgorithm != x509.SHA384WithRSAPSS { return nil, fmt.Errorf("%v certificate signature algorithm is %v, expected SHA-384 with RSASSA-PSS", key, cert.SignatureAlgorithm) } // Subject Public Key Info ECDSA on curve P-384 if cert.PublicKeyAlgorithm != x509.ECDSA { return nil, fmt.Errorf("%v certificate public key type is %v, expected ECDSA", key, cert.PublicKeyAlgorithm) } // Locally bind the public key any type to allow for occurrence typing in the switch statement. switch pub := cert.PublicKey.(type) { case *ecdsa.PublicKey: if pub.Curve.Params().Name != "P-384" { return nil, fmt.Errorf("%v certificate public key curve is %s, expected P-384", key, pub.Curve.Params().Name) } default: return nil, fmt.Errorf("%v certificate public key not ecdsa PublicKey type %v", key, pub) } if err := validateKDSCertSubject(cert.Subject, key); err != nil { return nil, err } exts, err := kds.CertificateExtensions(cert, key) if err != nil { return nil, err } if err := validateExtensions(exts, key); err != nil { return nil, err } return exts, nil } func validateKDSCertificateProductSpecifics(r *trust.AMDRootCerts, cert *x509.Certificate, key abi.ReportSigner, opts *Options) error { if err := validateKDSCertIssuer(r, cert.Issuer, key); err != nil { return err } // ica: Intermediate Certificate Authority. ica := r.ProductCerts.Ask if key == abi.VlekReportSigner { ica = r.ProductCerts.Asvk } if ica == nil { return fmt.Errorf("root of trust missing intermediate certificate authority certificate for key %v", key) } verifyOpts := r.X509Options(opts.Now, key) if verifyOpts == nil { return fmt.Errorf("internal error: could not get X509 options for %v (missing ARK cert or ICA cert)", key) } if _, err := cert.Verify(*verifyOpts); err != nil { return fmt.Errorf("error verifying %v certificate: %v (%v)", key, err, ica.IsCA) } // VCEK is not expected to have a CRL link. return nil } func checkProductName(got, want *spb.SevProduct, key abi.ReportSigner) error { // No constraint if want == nil { return nil } if got == nil { return fmt.Errorf("internal error: no product name") } if got.Name != want.Name { return fmt.Errorf("%v cert product name %v is not %v", key, got, want) } // The model stepping number is only part of the VLEK product name, not VLEK's. if key == abi.VcekReportSigner && want.MachineStepping != nil { if got.MachineStepping == nil { return fmt.Errorf("stepping value in VCEK certificate should not be nil") } if got.MachineStepping.Value != want.MachineStepping.Value && !*workaroundStepping { return fmt.Errorf("%v cert product stepping number 0x%X is not 0x%X", key, got.MachineStepping.Value, want.MachineStepping.Value) } } return nil } // decodeCerts checks that the V[CL]EK certificate matches expected fields // from the KDS specification and also that its certificate chain matches // hardcoded trusted root certificates from AMD. func decodeCerts(chain *spb.CertificateChain, key abi.ReportSigner, knownProductLine string, options *Options) (*x509.Certificate, *trust.AMDRootCerts, error) { var ek []byte switch key { case abi.VcekReportSigner: ek = chain.GetVcekCert() case abi.VlekReportSigner: ek = chain.GetVlekCert() } if len(ek) == 0 { return nil, nil, fmt.Errorf("missing %v certificate", key) } endorsementKeyCert, err := trust.ParseCert(ek) if err != nil { return nil, nil, fmt.Errorf("could not interpret %v DER bytes %v: %v", key, ek, err) } exts, err := validateKDSCertificateProductNonspecific(endorsementKeyCert, key) if err != nil { return nil, nil, err } roots := options.TrustedRoots productLine := knownProductLine // Relevant for v2 reports only. if productLine == "" { product, err := kds.ParseProductName(exts.ProductName, key) if err != nil { return nil, nil, err } productLine = kds.ProductLine(product) // Ensure the extension product info matches expectations. if err := checkProductName(product, options.Product, key); err != nil { return nil, nil, err } } if len(roots) == 0 { root := trust.AMDRootCertsProduct(productLine) // Require that the root matches embedded root certs. root.AskSev = trust.DefaultRootCerts[productLine].AskSev root.ArkSev = trust.DefaultRootCerts[productLine].ArkSev if err := root.Decode(chain.GetAskCert(), chain.GetArkCert()); err != nil { return nil, nil, err } if err := validateX509(root, key); err != nil { return nil, nil, err } roots = map[string][]*trust.AMDRootCerts{ productLine: {root}, } } var lastErr error for _, productRoot := range roots[productLine] { if err := validateKDSCertificateProductSpecifics(productRoot, endorsementKeyCert, key, options); err != nil { lastErr = err continue } return endorsementKeyCert, productRoot, nil } return nil, nil, fmt.Errorf("%v could not be verified by any trusted roots. Last error: %v", key, lastErr) } // SnpReportSignature verifies the attestation report's signature based on the report's // SignatureAlgo. func SnpReportSignature(report []byte, vcek *x509.Certificate) error { if err := abi.ValidateReportFormat(report); err != nil { return fmt.Errorf("attestation report format error: %v", err) } der, err := abi.ReportToSignatureDER(report) if err != nil { return fmt.Errorf("could not interpret report signature: %v", err) } if abi.SignatureAlgo(report) == abi.SignEcdsaP384Sha384 { if err := vcek.CheckSignature(x509.ECDSAWithSHA384, abi.SignedComponent(report), der); err != nil { return fmt.Errorf("report signature verification error: %v", err) } return nil } return fmt.Errorf("unknown SignatureAlgo: %d", abi.SignatureAlgo(report)) } // SnpProtoReportSignature verifies the protobuf representation of an attestation report's signature // based on the report's SignatureAlgo. func SnpProtoReportSignature(report *spb.Report, vcek *x509.Certificate) error { raw, err := abi.ReportToAbiBytes(report) if err != nil { return fmt.Errorf("could not interpret report: %v", err) } return SnpReportSignature(raw, vcek) } // Options represents verification options for an SEV-SNP attestation report. type Options struct { // CheckRevocations set to true if the verifier should retrieve the CRL from the network and check // if the VCEK or ASK have been revoked according to the ARK. CheckRevocations bool // DisableCertFetching set to true if SnpAttestation should not connect to the AMD KDS to fill in // any missing certificates in an attestation's certificate chain. Uses Getter if false. DisableCertFetching bool // Getter takes a URL and returns the body of its contents. By default uses http.Get and returns // the body. Getter trust.HTTPSGetter // Now is the time at which to verify the validity of certificates. If unset, uses time.Now(). Now time.Time // TrustedRoots specifies the ARK and ASK certificates to trust when checking the VCEK. If nil, // then verification will fall back on embedded AMD-published root certificates. // Maps the product name to an array of allowed roots. TrustedRoots map[string][]*trust.AMDRootCerts // Product is a forced value for the attestation product name when verifying or retrieving // VCEK certificates. An attestation should carry the product of the reporting // machine. Only used for v2 attestation reports. Product *spb.SevProduct } // DefaultOptions returns a useful default verification option setting func DefaultOptions() *Options { return &Options{ Getter: trust.DefaultHTTPSGetter(), Now: time.Now(), } } func getTrustedRoots(rot *cpb.RootOfTrust) (map[string][]*trust.AMDRootCerts, error) { result := map[string][]*trust.AMDRootCerts{} for _, path := range rot.CabundlePaths { root := trust.AMDRootCertsProduct(rot.ProductLine) if err := root.FromKDSCert(path); err != nil { return nil, fmt.Errorf("could not parse CA bundle %q: %v", path, err) } result[rot.ProductLine] = append(result[rot.ProductLine], root) } for _, cabundle := range rot.Cabundles { root := trust.AMDRootCertsProduct(rot.ProductLine) if err := root.FromKDSCertBytes([]byte(cabundle)); err != nil { return nil, fmt.Errorf("could not parse CA bundle bytes: %v", err) } result[rot.ProductLine] = append(result[rot.ProductLine], root) } return result, nil } // RootOfTrustToOptions translates the RootOfTrust message into the Options type needed // for driving an attestation verification. func RootOfTrustToOptions(rot *cpb.RootOfTrust) (*Options, error) { trustedRoots, err := getTrustedRoots(rot) if err != nil { return nil, err } return &Options{ CheckRevocations: rot.CheckCrl, DisableCertFetching: rot.DisallowNetwork, TrustedRoots: trustedRoots, }, nil } func updateProductExpectation(product **spb.SevProduct, reportProduct *spb.SevProduct) error { // The expected product in verification options may be under-specified or conflicting with a given // product, so check and update the expectation to match the reported product info. if *product == nil { if *workaroundStepping && reportProduct != nil { *product = &spb.SevProduct{Name: reportProduct.Name} } else { *product = reportProduct } return nil } if (*product).GetName() != reportProduct.GetName() { return fmt.Errorf("expected product name %v, got %v", (*product).GetName(), reportProduct.GetName()) } expectStepping := (*product).GetMachineStepping() reportStepping := reportProduct.GetMachineStepping() if expectStepping == nil { if !*workaroundStepping { (*product).MachineStepping = reportStepping } return nil } if reportStepping == nil { return nil } // This check is not skipped for Issue#115 since we only want to skip inferred stepping, not // explicitly expected stepping. if expectStepping.GetValue() != reportStepping.GetValue() { return fmt.Errorf("expected product stepping %d, got %d", expectStepping.Value, reportStepping.Value) } return nil } // SnpAttestation verifies the protobuf representation of an attestation report's signature based // on the report's SignatureAlgo, provided the certificate chain is valid. func SnpAttestation(attestation *spb.Attestation, options *Options) error { if options == nil { return fmt.Errorf("options cannot be nil") } if attestation == nil { return fmt.Errorf("attestation cannot be nil") } // Make sure we have the whole certificate chain, or at least the product // info. if err := fillInAttestation(attestation, options); err != nil { return err } report := attestation.GetReport() info, err := abi.ParseSignerInfo(report.GetSignerInfo()) if err != nil { return err } chain := attestation.GetCertificateChain() var knownProductLine string if fms := attestation.GetReport().GetCpuid1EaxFms(); fms != 0 { knownProductLine = kds.ProductLineFromFms(fms) } endorsementKeyCert, root, err := decodeCerts(chain, info.SigningKey, knownProductLine, options) if err != nil { return err } if options.CheckRevocations { if err := VcekNotRevoked(root, endorsementKeyCert, options); err != nil { return err } } return SnpProtoReportSignature(report, endorsementKeyCert) } func getProductFromCerts(attestation *spb.Attestation) *spb.SevProduct { certs := abi.CertsFromProto(attestation.CertificateChain) blob, err := certs.GetByGUIDString(abi.ExtraPlatformInfoGUID) if err != nil { return nil } info, err := abi.ParseExtraPlatformInfo(blob) if err != nil { return nil } return abi.SevProductFromCpuid1Eax(info.Cpuid1Eax) } // Returns the product information in the attestation. func getProduct(attestation *spb.Attestation) *spb.SevProduct { product := getProductFromCerts(attestation) if product != nil { return product } // TODO(Issue#109): Remove. return attestation.Product } // Updates the attestation representation of the product. This is lossy given the product -> Cpuid1Eax translation. func setProduct(attestation *spb.Attestation, product *spb.SevProduct) { blob, _ := (&abi.ExtraPlatformInfo{ Size: abi.ExtraPlatformInfoV0Size, Cpuid1Eax: abi.MaskedCpuid1EaxFromSevProduct(product), }).Marshal() attestation.CertificateChain.Extras[abi.ExtraPlatformInfoGUID] = blob // TODO(Issue#109): Remove attestation.Product = product } // In version 2 attestation reports, there is no cpuid_1_eax information about the // family/model/stepping of the chip, so it's difficult to derive the endpoint from which to // fetch a VCEK certificate. // In version 3 attestation reports, the information is present, so we can directly return // the product line from those fields of the report. // // The result values are a product line string, a method of updating product information when there // is no explicit product expectation from options, and a method of updating the product expectation // when relevant. This can correct any inaccuracy about a stepping value. // For v3 reports, these update functions are trivial, as there are no inaccuracies to correct when // the information is directly in the attestation report. func cpuidWorkaround(attestation *spb.Attestation, options *Options) (string, func([]byte) error, func() error, error) { productUpdate := func([]byte) error { return nil } updateExpectation := func() error { return nil } fms := attestation.GetReport().GetCpuid1EaxFms() if fms != 0 { return kds.ProductLineFromFms(fms), productUpdate, updateExpectation, nil } // ATTESTATION_REPORT v2 makes product determination difficult. product := getProduct(attestation) if product == nil { if options.Product != nil { product = options.Product } else { logger.Warning("Attestation missing product information. KDS certificate may be invalid. Using default Milan-B1") attestation.Product = abi.DefaultSevProduct() } productUpdate = func(vcek []byte) error { cert, err := x509.ParseCertificate(vcek) if err != nil { return err } exts, err := kds.VcekCertificateExtensions(cert) if err != nil { return err } product, err = kds.ParseProductName(exts.ProductName, abi.VcekReportSigner) if err != nil { return err } return nil } } updateExpectation = func() error { // Pass along the expected product information for VcekDER. fillInAttestation will ensure // that this is a noop if options.Product began as non-nil. return updateProductExpectation(&options.Product, product) } return kds.ProductLine(product), productUpdate, updateExpectation, nil } // fillInAttestation uses AMD's KDS to populate any empty certificate field in the attestation's // certificate chain. func fillInAttestation(attestation *spb.Attestation, options *Options) error { if options.DisableCertFetching { return nil } productLine, productUpdate, updateExpectation, err := cpuidWorkaround(attestation, options) if err != nil { return err } getter := options.Getter if getter == nil { getter = trust.DefaultHTTPSGetter() } report := attestation.GetReport() info, err := abi.ParseSignerInfo(report.GetSignerInfo()) if err != nil { return err } chain := attestation.GetCertificateChain() if chain == nil { chain = &spb.CertificateChain{} attestation.CertificateChain = chain } if len(chain.GetAskCert()) == 0 || len(chain.GetArkCert()) == 0 { askark, err := trust.GetProductChain(productLine, info.SigningKey, getter) if err != nil { return err } if len(chain.GetAskCert()) == 0 { chain.AskCert = askark.Ask.Raw } if len(chain.GetArkCert()) == 0 { chain.ArkCert = askark.Ark.Raw } } switch info.SigningKey { case abi.VcekReportSigner: if len(chain.GetVcekCert()) == 0 { vcekURL := kds.VCEKCertURL(productLine, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb())) vcek, err := getter.Get(vcekURL) if err != nil { return &trust.AttestationRecreationErr{ Msg: fmt.Sprintf("could not download VCEK certificate: %v", err), } } chain.VcekCert = vcek // An attempt was made with defaults or the option's product, so now use // the VCEK cert to determine the real product info. if err := productUpdate(vcek); err != nil { return err } } case abi.VlekReportSigner: // We can't lazily ask KDS for the certificate as a user. The CSP must cache their provisioned // certificates and provide them in GET_EXT_REPORT. if len(chain.GetVlekCert()) == 0 { return ErrMissingVlek } } return updateExpectation() } // GetAttestationFromReport uses AMD's Key Distribution Service (KDS) to download the certificate // chain for the VCEK that supposedly signed the given report, and returns the Attestation // representation of their combination. If getter is nil, uses Golang's http.Get. func GetAttestationFromReport(report *spb.Report, options *Options) (*spb.Attestation, error) { result := &spb.Attestation{ Report: report, CertificateChain: &spb.CertificateChain{Extras: map[string][]byte{}}, } if err := fillInAttestation(result, options); err != nil { return nil, err } // Attempt to fill in the product field of the attestation. Don't error at this // point since this is not validation. info, _ := abi.ParseSignerInfo(report.SignerInfo) var exts *kds.Extensions parse := func(der []byte) *x509.Certificate { out, _ := x509.ParseCertificate(der) return out } switch info.SigningKey { case abi.VcekReportSigner: exts, _ = kds.VcekCertificateExtensions(parse(result.CertificateChain.VcekCert)) case abi.VlekReportSigner: exts, _ = kds.VlekCertificateExtensions(parse(result.CertificateChain.VlekCert)) } if exts != nil && report.GetCpuid1EaxFms() == 0 { product, _ := kds.ParseProductName(exts.ProductName, info.SigningKey) setProduct(result, product) } return result, nil } // SnpReport verifies the protobuf representation of an attestation report's signature based // on the report's SignatureAlgo and uses the AMD Key Distribution Service to download the // report's corresponding VCEK certificate. func SnpReport(report *spb.Report, options *Options) error { if options.DisableCertFetching { return errors.New("cannot verify attestation report without fetching certificates") } attestation, err := GetAttestationFromReport(report, options) if err != nil { return fmt.Errorf("could not recreate attestation from report: %w", err) } return SnpAttestation(attestation, options) } // RawSnpReport verifies the raw bytes representation of an attestation report's signature // based on the report's SignatureAlgo and uses the AMD Key Distribution Service to download // the report's corresponding VCEK certificate. func RawSnpReport(rawReport []byte, options *Options) error { report, err := abi.ReportToProto(rawReport) if err != nil { return fmt.Errorf("could not interpret report bytes: %v", err) } return SnpReport(report, options) } go-sev-guest-0.13.0/verify/verify_test.go000066400000000000000000000661761476044460400203470ustar00rootroot00000000000000// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "bytes" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" _ "embed" "encoding/asn1" "encoding/pem" "flag" "fmt" "math/big" "math/rand" "os" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-sev-guest/abi" sg "github.com/google/go-sev-guest/client" "github.com/google/go-sev-guest/kds" spb "github.com/google/go-sev-guest/proto/sevsnp" test "github.com/google/go-sev-guest/testing" testclient "github.com/google/go-sev-guest/testing/client" "github.com/google/go-sev-guest/verify/testdata" "github.com/google/go-sev-guest/verify/trust" "github.com/google/logger" "google.golang.org/protobuf/types/known/wrapperspb" ) var ( signMu sync.Once signer *test.AmdSigner requireCache = flag.Bool("require_cert_cache", true, "If true, hardware tests depend on host cache of endorsement key certificates") insecureRandomness = rand.New(rand.NewSource(0xc0de)) ) func initSigner() { newSigner, err := test.DefaultTestOnlyCertChain(test.GetProductName(), time.Now()) if err != nil { // Unexpected panic(err) } signer = newSigner } func TestMain(m *testing.M) { logger.Init("VerifyTestLog", false, false, os.Stderr) os.Exit(m.Run()) } func TestEmbeddedCertsAppendixB3Expectations(t *testing.T) { // https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf // Appendix B.1 for _, root := range trust.DefaultRootCerts { // Genoa does not use the deprecated key format. if root.ArkSev == nil || root.AskSev == nil { continue } if err := validateAskSev(root); err != nil { t.Errorf("Embedded ASK failed validation: %v", err) } if err := validateArkSev(root); err != nil { t.Errorf("Embedded ARK failed validation: %v", err) } } } func TestFakeCertsKDSExpectations(t *testing.T) { for _, productLine := range kds.ProductLineCpuid { trust.ClearProductCertCache() signer, err := test.DefaultTestOnlyCertChain(productLine+"-B0", time.Now()) if err != nil { t.Fatalf("no quote provider for productLine %s: %v", productLine, err) } root := trust.AMDRootCertsProduct(productLine) root.ProductCerts = &trust.ProductCerts{ Ark: signer.Ark, Ask: signer.Ask, } // No ArkSev or AskSev intentionally for test certs. if err := validateArkX509(root); err != nil { t.Errorf("fake ARK validation error: %v", err) } if err := validateAskX509(root); err != nil { t.Errorf("fake ASK validation error: %v", err) } } } func TestParseVcekCert(t *testing.T) { cert, err := x509.ParseCertificate(testdata.VcekBytes) if err != nil { t.Errorf("could not parse valid VCEK certificate: %v", err) } if _, err := validateKDSCertificateProductNonspecific(cert, abi.VcekReportSigner); err != nil { t.Errorf("could not validate valid VCEK certificate: %v", err) } } func TestVerifyVcekCert(t *testing.T) { // This certificate is committed regardless of its expiration date, but we'll adjust the // CurrentTime to compare against so that the validity with respect to time is always true. root := new(trust.AMDRootCerts) if err := root.FromKDSCertBytes(trust.AskArkMilanVcekBytes); err != nil { t.Fatalf("could not read Milan certificate file: %v", err) } vcek, err := x509.ParseCertificate(testdata.VcekBytes) if err != nil { t.Errorf("could not parse valid VCEK certificate: %v", err) } now := time.Date(2022, time.September, 24, 1, 0, 0, 0, time.UTC) opts := root.X509Options(now, abi.VcekReportSigner) if opts == nil { t.Fatalf("root x509 certificates missing: %v", root) return } // This time is within the 25 year lifespan of the Milan product. chains, err := vcek.Verify(*opts) if err != nil { t.Errorf("could not verify VCEK certificate: %v", err) } if len(chains) != 1 { t.Fatalf("x509 verification returned %d chains, want 1", len(chains)) } if len(chains[0]) != 3 { t.Fatalf("x509 verification returned a chain of length %d, want length 3", len(chains[0])) } if !chains[0][0].Equal(vcek) { t.Errorf("VCEK verification chain did not start with the VCEK certificate: %v", chains[0][0]) } if !chains[0][1].Equal(root.ProductCerts.Ask) { t.Errorf("VCEK verification chain did not step to with the ASK certificate: %v", chains[0][1]) } if !chains[0][2].Equal(root.ProductCerts.Ark) { t.Errorf("VCEK verification chain did not end with the ARK certificate: %v", chains[0][2]) } } func TestSnpReportSignature(t *testing.T) { tests := test.TestCases() now := time.Date(2022, time.May, 3, 9, 0, 0, 0, time.UTC) qps := map[uint32]*test.QuoteProvider{} for fms, productLine := range kds.ProductLineCpuid { p, _ := kds.ParseProductLine(productLine) if p == nil { t.Fatal("productLine parsing failed") } qp, err := test.TcQuoteProvider(tests, &test.DeviceOptions{Now: now, Product: p}) if err != nil { t.Fatal(err) } qps[fms] = qp } for _, tc := range tests { if testclient.SkipUnmockableTestCase(&tc) { continue } fms := fmsFromReport(t, tc.Output[:]) qp := qps[fms&^0xf] if qp == nil { t.Fatalf("No quote provider for fms 0x%x", fms) } // Does the Raw report match expectations? rawcombo, err := qp.GetRawQuote(tc.Input) if !test.Match(err, tc.WantErr) || (tc.WantErr == "" && len(rawcombo) < abi.ReportSize) { t.Fatalf("GetRawQuote(qp, %v) = %v, %v. Want err: %q", tc.Input, rawcombo, err, tc.WantErr) } if tc.WantErr == "" { raw := rawcombo[:abi.ReportSize] got := abi.SignedComponent(raw) want := abi.SignedComponent(tc.Output[:]) if diff := cmp.Diff(want, got); diff != "" { t.Errorf("%s: GetRawReport(%v) = %v, want %v\nDiff (-want, +got): %s", tc.Name, tc.Input, got, want, diff) } key := qp.Device.Signer.Vcek if tc.EK == test.KeyChoiceVlek { key = qp.Device.Signer.Vlek } if err := SnpReportSignature(raw, key); err != nil { t.Errorf("signature with test keys did not verify: %v", err) } } } } func TestKdsMetadataLogic(t *testing.T) { signMu.Do(initSigner) trust.ClearProductCertCache() asn1Zero, _ := asn1.Marshal(0) productName, _ := asn1.MarshalWithParams("Cookie-B0", "ia5") var hwid [64]byte asn1Hwid, _ := asn1.Marshal(hwid[:]) tests := []struct { name string builder test.AmdSignerBuilder wantErr string }{ { name: "no error", builder: test.AmdSignerBuilder{Keys: signer.Keys}, }, { name: "ARK issuer country", builder: test.AmdSignerBuilder{ Keys: signer.Keys, ArkCustom: test.CertOverride{ Issuer: &pkix.Name{Country: []string{"Canada"}}, Subject: &pkix.Name{Country: []string{"Canada"}}, }, }, wantErr: "country 'Canada' not expected for AMD. Expected 'US'", }, { name: "ARK wrong CRL", builder: test.AmdSignerBuilder{ Keys: signer.Keys, ArkCustom: test.CertOverride{ CRLDistributionPoints: []string{"http://example.com"}, }, }, wantErr: fmt.Sprintf("ARK CRL distribution point is 'http://example.com', want 'https://kdsintf.amd.com/vcek/v1/%s/crl'", test.GetProductLine()), }, { name: "ARK too many CRLs", builder: test.AmdSignerBuilder{ Keys: signer.Keys, ArkCustom: test.CertOverride{ CRLDistributionPoints: []string{fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/%s/crl", test.GetProductLine()), "http://example.com"}, }, }, wantErr: "ARK has 2 CRL distribution points, want 1", }, { name: "ASK subject state", builder: test.AmdSignerBuilder{ Keys: signer.Keys, ArkCustom: test.CertOverride{ Subject: &pkix.Name{ Country: []string{"US"}, Locality: []string{"Santa Clara"}, Province: []string{"TX"}, }, }, }, wantErr: "state 'TX' not expected for AMD. Expected 'CA'", }, { name: "VCEK unknown product", builder: test.AmdSignerBuilder{ Keys: signer.Keys, VcekCustom: test.CertOverride{ Extensions: []pkix.Extension{ { Id: kds.OidStructVersion, Value: asn1Zero, }, { Id: kds.OidProductName1, Value: productName, }, { Id: kds.OidBlSpl, Value: asn1Zero, }, { Id: kds.OidTeeSpl, Value: asn1Zero, }, { Id: kds.OidSnpSpl, Value: asn1Zero, }, { Id: kds.OidSpl4, Value: asn1Zero, }, { Id: kds.OidSpl5, Value: asn1Zero, }, { Id: kds.OidSpl6, Value: asn1Zero, }, { Id: kds.OidSpl7, Value: asn1Zero, }, { Id: kds.OidUcodeSpl, Value: asn1Zero, }, { Id: kds.OidHwid, Value: asn1Hwid, }, }, }, }, wantErr: "unknown product", }, } for _, tc := range tests { bcopy := tc.builder newSigner, err := (&bcopy).TestOnlyCertChain() if err != nil { t.Errorf("%+v.TestOnlyCertChain() errored unexpectedly: %v", tc.builder, err) continue } // Trust the test-generated root if the test should pass. Otherwise, other root logic // won't get tested. options := &Options{ TrustedRoots: map[string][]*trust.AMDRootCerts{ test.GetProductLine(): {func() *trust.AMDRootCerts { r := trust.AMDRootCertsProduct(test.GetProductLine()) r.ProductCerts = &trust.ProductCerts{ Ark: newSigner.Ark, Ask: newSigner.Ask, } return r }()}, }, Now: time.Date(1, time.January, 5, 0, 0, 0, 0, time.UTC), Product: abi.DefaultSevProduct(), } if tc.wantErr != "" { options = &Options{Product: abi.DefaultSevProduct()} } vcekPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: newSigner.Vcek.Raw}) vcek, _, err := decodeCerts(&spb.CertificateChain{VcekCert: vcekPem, AskCert: newSigner.Ask.Raw, ArkCert: newSigner.Ark.Raw}, abi.VcekReportSigner, "", options) if !test.Match(err, tc.wantErr) { t.Errorf("%s: decodeCerts(...) = %+v, %v did not error as expected. Want %q", tc.name, vcek, err, tc.wantErr) } } } func TestCRLRootValidity(t *testing.T) { // Tests that the CRL is signed by the ARK. signMu.Do(initSigner) trust.ClearProductCertCache() now := time.Date(2022, time.June, 14, 12, 0, 0, 0, time.UTC) ark2, err := rsa.GenerateKey(insecureRandomness, 4096) if err != nil { t.Fatal(err) } sb := &test.AmdSignerBuilder{ ProductName: test.GetProductName(), ArkCreationTime: now, AskCreationTime: now, VcekCreationTime: now, CSPID: "go-sev-guest", Keys: &test.AmdKeys{ Ark: ark2, Ask: signer.Keys.Ask, Asvk: signer.Keys.Asvk, Vcek: signer.Keys.Vcek, Vlek: signer.Keys.Vlek, }, VcekCustom: test.CertOverride{ SerialNumber: big.NewInt(0xd), }, AskCustom: test.CertOverride{ SerialNumber: big.NewInt(0x8088), }, } signer2, err := sb.TestOnlyCertChain() if err != nil { t.Fatal(err) } afterCreation := now.Add(1 * time.Minute) template := &x509.RevocationList{ SignatureAlgorithm: x509.SHA384WithRSAPSS, RevokedCertificates: []pkix.RevokedCertificate{ // The default fake VCEK serial number is 0. {SerialNumber: big.NewInt(0), RevocationTime: afterCreation}, {SerialNumber: big.NewInt(0x8088), RevocationTime: afterCreation}, }, Number: big.NewInt(1), } root := trust.AMDRootCertsProduct(test.GetProductLine()) root.ProductCerts = &trust.ProductCerts{ Ark: signer.Ark, Ask: signer.Ask, } // Now try signing a CRL with a different root that certifies Vcek with a different serial number. crl, err := x509.CreateRevocationList(insecureRandomness, template, signer2.Ark, signer2.Keys.Ark) if err != nil { t.Fatal(err) } g2 := test.SimpleGetter( map[string][]byte{ fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/%s/crl", test.GetProductLine()): crl, }, ) wantErr := "CRL is not signed by ARK" if err := VcekNotRevoked(root, signer2.Vcek, &Options{Getter: g2}); !test.Match(err, wantErr) { t.Errorf("Bad Root: VcekNotRevoked(%v) did not error as expected. Got %v, want %v", signer.Vcek, err, wantErr) } // Finally try checking a VCEK that's signed by a revoked ASK. root2 := trust.AMDRootCertsProduct(test.GetProductLine()) root2.ProductCerts = &trust.ProductCerts{ Ark: signer2.Ark, Ask: signer2.Ask, } wantErr2 := "ASK was revoked at 2022-06-14 12:01:00 +0000 UTC" if err := VcekNotRevoked(root2, signer2.Vcek, &Options{Getter: g2}); !test.Match(err, wantErr2) { t.Errorf("Bad ASK: VcekNotRevoked(%v) did not error as expected. Got %v, want %v", signer.Vcek, err, wantErr2) } } type reportGetter func(sg.QuoteProvider, [64]byte) (*spb.Attestation, error) type reportGetterProfile struct { name string getter reportGetter skipVlek bool skipNoCache bool badRootErr string vlekOnly bool vlekErr string vlekBadRootErr string } type providerCache struct { tcs []test.TestCase opts *test.DeviceOptions entries map[uint32]*providerData } type providerData struct { qp sg.QuoteProvider badRoots map[string][]*trust.AMDRootCerts opts *Options } func (p *providerCache) getStore() map[uint32]*providerData { if p.entries == nil { p.entries = map[uint32]*providerData{} } return p.entries } func (p *providerCache) forceProvider(t testing.TB, fms uint32) *providerData { store := p.getStore() if data, ok := store[fms]; ok { return data } dopts := *p.opts dopts.Product = abi.SevProductFromCpuid1Eax(fms) qp, goodRoots, badRoots, kds := testclient.GetSevQuoteProvider(p.tcs, &dopts, t) // Trust the test device's root certs. options := &Options{ TrustedRoots: goodRoots, Getter: kds, Product: dopts.Product, DisableCertFetching: *requireCache && !sg.UseDefaultSevGuest(), } data := &providerData{ qp: qp, badRoots: badRoots, opts: options, } store[fms] = data return data } func fullQuoteTest(t *testing.T, pd *providerData, getReport *reportGetterProfile, tc *test.TestCase) { // On real hardware, skip tests that represent being on a different platform. if pd.qp == nil { t.Skip() return } ereport, err := getReport.getter(pd.qp, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("(d, %v) = %v, %v. Want err: %v", tc.Input, ereport, err, tc.WantErr) } if tc.WantErr != "" { return } var wantAttestationErr string if tc.EK == test.KeyChoiceVlek && getReport.vlekErr != "" { wantAttestationErr = getReport.vlekErr } if err := SnpAttestation(ereport, pd.opts); !test.Match(err, wantAttestationErr) { t.Errorf("SnpAttestation(%v) = %v. Want err: %q", ereport, err, wantAttestationErr) } wantBad := getReport.badRootErr if tc.EK == test.KeyChoiceVlek && getReport.vlekBadRootErr != "" { wantBad = getReport.vlekBadRootErr } badOptions := &Options{TrustedRoots: pd.badRoots, Getter: pd.opts.Getter, Product: pd.opts.Product} if err := SnpAttestation(ereport, badOptions); !test.Match(err, wantBad) { t.Errorf("SnpAttestation(_) bad root test errored unexpectedly: %v, want %s", err, wantBad) } } func fmsFromReport(t testing.TB, report []byte) uint32 { fms := abi.FmsToCpuid1Eax(report[0x188], report[0x189], report[0x18A]) if fms == 0 { fms = abi.MaskedCpuid1EaxFromSevProduct(test.GetProduct(t)) } return fms } // TestOpenGetExtendedReportVerifyClose tests the SnpAttestation function for the deprecated ioctl // API. func TestOpenGetExtendedReportVerifyClose(t *testing.T) { trust.ClearProductCertCache() tests := test.TestCases() reportOnly := func(qp sg.QuoteProvider, input [64]byte) (*spb.Attestation, error) { attestation, err := sg.GetQuoteProto(qp, input) if err != nil { return nil, err } return &spb.Attestation{Report: attestation.Report}, nil } reportGetters := []*reportGetterProfile{ { name: "GetExtendedReport", getter: sg.GetQuoteProto, badRootErr: "error verifying VCEK certificate", vlekBadRootErr: "error verifying VLEK certificate", }, { name: "GetReport", getter: reportOnly, badRootErr: "error verifying VCEK certificate", vlekErr: "VLEK certificate is missing", vlekBadRootErr: "VLEK certificate is missing", skipNoCache: true, }, { name: "GetReportVlek", getter: func(qp sg.QuoteProvider, input [64]byte) (*spb.Attestation, error) { attestation, err := reportOnly(qp, input) if err != nil { return nil, err } // If fake, we can provide the VLEK. Otherwise we have to error. if attestation.CertificateChain == nil { attestation.CertificateChain = &spb.CertificateChain{} } chain := attestation.CertificateChain // Forge VLEK signer info since all test cases assume VCEK. attestation.Report.SignerInfo = abi.ComposeSignerInfo(abi.SignerInfo{ SigningKey: abi.VlekReportSigner, }) if sg.UseDefaultSevGuest() { if td, ok := qp.(*test.QuoteProvider); ok { chain.VlekCert = td.Device.Signer.Vlek.Raw } } return attestation, nil }, skipVlek: !sg.UseDefaultSevGuest(), vlekOnly: true, badRootErr: "error verifying VLEK certificate", vlekBadRootErr: "error verifying VLEK certificate", skipNoCache: true, }, } providerCache := &providerCache{tcs: test.TestCases(), opts: &test.DeviceOptions{Now: time.Now()}} for _, tc := range tests { if testclient.SkipUnmockableTestCase(&tc) { t.Run(tc.Name, func(t *testing.T) { t.Skip() }) continue } for _, getReport := range reportGetters { t.Run(tc.Name+"_"+getReport.name, func(t *testing.T) { trust.ClearProductCertCache() if getReport.skipVlek && tc.EK == test.KeyChoiceVlek { t.Skip() return } if getReport.vlekOnly && tc.EK != test.KeyChoiceVlek { t.Skip() return } if getReport.skipNoCache && *requireCache { t.Skip() return } // If the test case is for a v3 report and the products don't align with // the expected product, skip. fms := fmsFromReport(t, tc.Output[:]) fullQuoteTest(t, providerCache.forceProvider(t, fms), getReport, &tc) }) } } } // TestGetQuoteProviderVerify tests the SnpAttestation function for the configfs-tsm report API. func TestGetQuoteProviderVerify(t *testing.T) { trust.ClearProductCertCache() tests := test.TestCases() providerCache := &providerCache{tcs: tests, opts: &test.DeviceOptions{Now: time.Now()}} for _, tc := range tests { // configfs-tsm doesn't support the key choice parameter for getting an attestation report, and // it doesn't return firmware error codes. if testclient.SkipUnmockableTestCase(&tc) || tc.EK == test.KeyChoiceVlek { t.Run(tc.Name, func(t *testing.T) { t.Skip() }) continue } t.Run(tc.Name+"_", func(t *testing.T) { pd := providerCache.forceProvider(t, fmsFromReport(t, tc.Output[:])) if pd.qp == nil { t.Skip() return } reportcerts, err := pd.qp.GetRawQuote(tc.Input) ereport, _ := abi.ReportCertsToProto(reportcerts) if tc.FwErr != abi.Success { if err == nil { t.Fatalf("(d, %v) = %v. Unexpected success given firmware error: %v", tc.Input, ereport, tc.FwErr) } } else if !test.Match(err, tc.WantErr) { t.Fatalf("(d, %v) = %v, %v. Want err: %v", tc.Input, ereport, err, tc.WantErr) } if tc.WantErr == "" { var wantAttestationErr string if err := SnpAttestation(ereport, pd.opts); !test.Match(err, wantAttestationErr) { t.Errorf("SnpAttestation(%v) = %v. Want err: %q", ereport, err, wantAttestationErr) } badOptions := &Options{TrustedRoots: pd.badRoots, Getter: pd.opts.Getter, Product: pd.opts.Product} wantBad := "error verifying VCEK certificate" if err := SnpAttestation(ereport, badOptions); !test.Match(err, wantBad) { t.Errorf("SnpAttestation(_) bad root test errored unexpectedly: %v, want %s", err, wantBad) } } }) } } func TestGetQuoteProviderVerifyProductNameSteppingMismatch(t *testing.T) { if !sg.UseDefaultSevGuest() { t.Skip("Cannot override true cpuid in hardware for negative testing") return } trust.ClearProductCertCache() tests := test.TestCases() signerMilan0, err := test.DefaultTestOnlyCertChain("Milan-B0", time.Now()) if err != nil { t.Fatal(err) } qp, goodRoots, _, kds := testclient.GetSevQuoteProvider(tests, &test.DeviceOptions{ Now: time.Now(), Signer: signerMilan0, // Mismatch cpuid product with certs. Product: &spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: wrapperspb.UInt32(1)}, }, t) tc := tests[0] options := &Options{ TrustedRoots: goodRoots, Getter: kds, DisableCertFetching: *requireCache && !sg.UseDefaultSevGuest(), } withProduct := func(p *spb.SevProduct) *Options { op := *options op.Product = p return &op } reportcerts, err := qp.GetRawQuote(tc.Input) if err != nil { t.Fatal(err) } ereport, _ := abi.ReportCertsToProto(reportcerts) ops := []struct { name string options *Options workaround115 bool wantErr string }{ { name: "no product expectation with workaround", options: withProduct(nil), workaround115: true, }, { name: "no product expectation without workaround", options: withProduct(nil), wantErr: "0x0 is not 0x1", // decodeCerts error }, { name: "Milan expectation without stepping, with workaround", options: withProduct(&spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN}), workaround115: true, }, { name: "Milan expectation without stepping without workaround", options: withProduct(&spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN}), wantErr: "0x0 is not 0x1", // decodeCerts error }, { name: "Milan-B1 expectation without workaround", options: withProduct(&spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: wrapperspb.UInt32(1)}), wantErr: "0x0 is not 0x1", }, { name: "Milan-B0 expectation with workaround", options: withProduct(&spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: wrapperspb.UInt32(0)}), workaround115: true, // an explicit expectation should not step this check against CPUID. wantErr: "expected product stepping 0, got 1", }, { name: "Milan-B0 expectation without workaround", options: withProduct(&spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: wrapperspb.UInt32(0)}), wantErr: "expected product stepping 0, got 1", }, } origWorkaround := *workaroundStepping defer func() { *workaroundStepping = origWorkaround }() for _, op := range ops { t.Run(op.name, func(t *testing.T) { *workaroundStepping = op.workaround115 if err := SnpAttestation(ereport, op.options); !test.Match(err, op.wantErr) { t.Errorf("SnpAttestation(%v, %v) = %v. Want err: %q", ereport, op.options, err, op.wantErr) } }) } } func TestRealAttestationVerification(t *testing.T) { trust.ClearProductCertCache() var nonce [64]byte copy(nonce[:], []byte{1, 2, 3, 4, 5}) getter := test.SimpleGetter( map[string][]byte{ "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain": trust.AskArkMilanVcekBytes, // Use the VCEK's hwID and known TCB values to specify the URL its VCEK cert would be fetched from. "https://kdsintf.amd.com/vcek/v1/Milan/3ac3fe21e13fb0990eb28a802e3fb6a29483a6b0753590c951bdd3b8e53786184ca39e359669a2b76a1936776b564ea464cdce40c05f63c9b610c5068b006b5d?blSPL=2&teeSPL=0&snpSPL=5&ucodeSPL=68": testdata.VcekBytes, }, ) tcs := []struct { name string product *spb.SevProduct wantErr string }{ { name: "happy path", product: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 0}, }, }, { name: "bad vcek stepping", product: &spb.SevProduct{ Name: spb.SevProduct_SEV_PRODUCT_MILAN, MachineStepping: &wrapperspb.UInt32Value{Value: 12}, }, wantErr: "expected product stepping 12, got 0", }, } for _, tc := range tcs { opts := &Options{Getter: getter, Product: tc.product} if err := RawSnpReport(testdata.AttestationBytes, opts); !test.Match(err, tc.wantErr) { t.Errorf("RawSnpReport(_, %+v) = %v errored unexpectedly. Want %q", opts, err, tc.wantErr) } } } func TestKDSCertBackdated(t *testing.T) { if !test.TestUseKDS() { t.Skip() } getter := test.GetKDS(t) // Throttle requests to KDS. time.Sleep(10 * time.Second) bytes, err := getter.Get(fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/%s/3ac3fe21e13fb0990eb28a802e3fb6a29483a6b0753590c951bdd3b8e53786184ca39e359669a2b76a1936776b564ea464cdce40c05f63c9b610c5068b006b5d?blSPL=2&teeSPL=0&snpSPL=5&ucodeSPL=68", test.GetProductLine())) if err != nil { t.Skipf("Live KDS query failed: %v", err) } cert, err := x509.ParseCertificate(bytes) if err != nil { t.Fatalf("Could not parse live VCEK certificate: %v", err) } now := time.Now() if !cert.NotBefore.Before(now.Add(-23 * time.Hour)) { t.Fatalf("KDS has not backdated its certificates. NotBefore: %s, now: %s", cert.NotBefore.Format(time.RFC3339), now.Format(time.RFC3339)) } } func TestV3KDSProduct(t *testing.T) { var tcs []test.TestCase for _, tc := range test.TestCases() { if tc.Output[0] == 3 { t.Logf("picked %s", tc.Name) tcs = append(tcs, tc) } } if len(tcs) == 0 { t.Fatalf("no test cases") } getter := test.SimpleGetter(map[string][]byte{ "https://kdsintf.amd.com/vcek/v1/Milan/00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000?blSPL=0&teeSPL=0&snpSPL=0&ucodeSPL=0": []byte("milancert"), "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain": trust.AskArkMilanVcekBytes, "https://kdsintf.amd.com/vcek/v1/Genoa/00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000?blSPL=0&teeSPL=0&snpSPL=0&ucodeSPL=0": []byte("genoacert"), "https://kdsintf.amd.com/vcek/v1/Genoa/cert_chain": trust.AskArkGenoaVcekBytes, }) options := &Options{ TrustedRoots: map[string][]*trust.AMDRootCerts{}, Now: time.Date(1, time.January, 5, 0, 0, 0, 0, time.UTC), Product: abi.DefaultSevProduct(), Getter: getter, } for _, productLine := range []string{"Milan", "Genoa"} { r := trust.AMDRootCertsProduct(productLine) r.ProductCerts = &trust.ProductCerts{ Ark: signer.Ark, Ask: signer.Ask, } options.TrustedRoots[productLine] = []*trust.AMDRootCerts{r} } var gotGenoa, gotMilan bool for _, tc := range tcs { t.Run(tc.Name, func(t *testing.T) { report, _ := abi.ReportToProto(tc.Output[:]) a := &spb.Attestation{Report: report} if err := fillInAttestation(a, options); err != nil { t.Fatalf("fillInAttestation(%v, %v) = %v, want nil", a, options, err) } var want []byte switch report.Cpuid1EaxFms { case 0x00a00f10: want = []byte("milancert") gotMilan = true case 0x00a10f10: want = []byte("genoacert") gotGenoa = true } got := a.CertificateChain.VcekCert if !bytes.Equal(got, want) { t.Fatalf("certificate is %v, want %v", got, want) } }) } if !gotMilan { t.Errorf("missed Milan case") } if !gotGenoa { t.Errorf("missed Genoa case") } }