pax_global_header 0000666 0000000 0000000 00000000064 14726101056 0014514 g ustar 00root root 0000000 0000000 52 comment=ef2fcc0c8b568a41488980c2f8bb109947e9ddb3
go-sev-guest-0.12.1/ 0000775 0000000 0000000 00000000000 14726101056 0014122 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/.github/ 0000775 0000000 0000000 00000000000 14726101056 0015462 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14726101056 0017517 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/.github/workflows/ci.yml 0000664 0000000 0000000 00000006014 14726101056 0020636 0 ustar 00root root 0000000 0000000 #
# 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:
jobs:
build:
strategy:
matrix:
go-version: [1.19.x, 1.20.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.19.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.19.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.12.1/.github/workflows/release.yml 0000664 0000000 0000000 00000002132 14726101056 0021660 0 ustar 00root root 0000000 0000000 name: 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.12.1/.gitignore 0000664 0000000 0000000 00000000052 14726101056 0016107 0 ustar 00root root 0000000 0000000 *
!*.*
!*/
*~
external/*
kdsdatabase.bin*
go-sev-guest-0.12.1/.goreleaser.yaml 0000664 0000000 0000000 00000002127 14726101056 0017216 0 ustar 00root root 0000000 0000000 builds:
- 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.12.1/CONTRIBUTING.md 0000664 0000000 0000000 00000002115 14726101056 0016352 0 ustar 00root root 0000000 0000000 # 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.12.1/INSTALL.md 0000664 0000000 0000000 00000002551 14726101056 0015555 0 ustar 00root root 0000000 0000000 # 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.12.1/LICENSE 0000664 0000000 0000000 00000026136 14726101056 0015137 0 ustar 00root root 0000000 0000000
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.12.1/LIMITATIONS.md 0000664 0000000 0000000 00000007215 14726101056 0016245 0 ustar 00root root 0000000 0000000 # 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.12.1/README.md 0000664 0000000 0000000 00000023256 14726101056 0015411 0 ustar 00root root 0000000 0000000 # 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.12.1/abi/ 0000775 0000000 0000000 00000000000 14726101056 0014655 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/abi/abi.go 0000664 0000000 0000000 00000114651 14726101056 0015747 0 ustar 00root root 0000000 0000000 // 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 = 1
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
// ReportVersion3 is set by the SNP API specification
// https://www.amd.com/system/files/TechDocs/56860.pdf
ReportVersion3 = 3
)
// 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
}
// 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 != ReportVersion2 && version != ReportVersion3 {
return fmt.Errorf("report version is: %d. Expected %d or %d", version, ReportVersion2, ReportVersion3)
}
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.12.1/abi/abi_test.go 0000664 0000000 0000000 00000052377 14726101056 0017014 0 ustar 00root root 0000000 0000000 // 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: 4,
wantErr: "unrecognized platform info bit(s): 0x4",
},
}
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.12.1/abi/amdsp.go 0000664 0000000 0000000 00000014464 14726101056 0016321 0 ustar 00root root 0000000 0000000 // 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.12.1/abi/cpuid.go 0000664 0000000 0000000 00000000240 14726101056 0016304 0 ustar 00root root 0000000 0000000 //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.12.1/abi/cpuid_amd64.go 0000664 0000000 0000000 00000000230 14726101056 0017276 0 ustar 00root root 0000000 0000000 //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.12.1/abi/cpuid_amd64.s 0000664 0000000 0000000 00000001466 14726101056 0017147 0 ustar 00root root 0000000 0000000 // 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.12.1/client/ 0000775 0000000 0000000 00000000000 14726101056 0015400 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/client/client.go 0000664 0000000 0000000 00000026255 14726101056 0017217 0 ustar 00root root 0000000 0000000 // 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.12.1/client/client_linux.go 0000664 0000000 0000000 00000021503 14726101056 0020425 0 ustar 00root root 0000000 0000000 // 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.12.1/client/client_macos.go 0000664 0000000 0000000 00000005042 14726101056 0020370 0 ustar 00root root 0000000 0000000 // 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.12.1/client/client_test.go 0000664 0000000 0000000 00000020716 14726101056 0020252 0 ustar 00root root 0000000 0000000 // 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.12.1/client/client_windows.go 0000664 0000000 0000000 00000005023 14726101056 0020757 0 ustar 00root root 0000000 0000000 // 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.12.1/client/linuxabi/ 0000775 0000000 0000000 00000000000 14726101056 0017213 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/client/linuxabi/linux_abi.go 0000664 0000000 0000000 00000024712 14726101056 0021522 0 ustar 00root root 0000000 0000000 // 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.12.1/go.mod 0000664 0000000 0000000 00000000660 14726101056 0015232 0 ustar 00root root 0000000 0000000 module 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.12.1/go.sum 0000664 0000000 0000000 00000004425 14726101056 0015262 0 ustar 00root root 0000000 0000000 github.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.12.1/kds/ 0000775 0000000 0000000 00000000000 14726101056 0014703 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/kds/kds.go 0000664 0000000 0000000 00000071724 14726101056 0016026 0 ustar 00root root 0000000 0000000 // 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.12.1/kds/kds_test.go 0000664 0000000 0000000 00000021423 14726101056 0017054 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/ 0000775 0000000 0000000 00000000000 14726101056 0015265 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/proto/check.proto 0000664 0000000 0000000 00000007325 14726101056 0017436 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/check/ 0000775 0000000 0000000 00000000000 14726101056 0016342 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/proto/check/check.pb.go 0000664 0000000 0000000 00000061605 14726101056 0020356 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/check/doc.go 0000664 0000000 0000000 00000001247 14726101056 0017442 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/doc.go 0000664 0000000 0000000 00000004241 14726101056 0016362 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/fakekds.proto 0000664 0000000 0000000 00000002165 14726101056 0017766 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/fakekds/ 0000775 0000000 0000000 00000000000 14726101056 0016675 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/proto/fakekds/doc.go 0000664 0000000 0000000 00000001536 14726101056 0017776 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/fakekds/fakekds.pb.go 0000664 0000000 0000000 00000022513 14726101056 0021237 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/sevsnp.proto 0000664 0000000 0000000 00000007276 14726101056 0017704 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/sevsnp/ 0000775 0000000 0000000 00000000000 14726101056 0016603 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/proto/sevsnp/doc.go 0000664 0000000 0000000 00000001262 14726101056 0017700 0 ustar 00root root 0000000 0000000 // 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.12.1/proto/sevsnp/sevsnp.pb.go 0000664 0000000 0000000 00000076676 14726101056 0021076 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/ 0000775 0000000 0000000 00000000000 14726101056 0015577 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/testing/client/ 0000775 0000000 0000000 00000000000 14726101056 0017055 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/testing/client/client.go 0000664 0000000 0000000 00000015605 14726101056 0020671 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/data/ 0000775 0000000 0000000 00000000000 14726101056 0016510 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/testing/data/data.go 0000664 0000000 0000000 00000006255 14726101056 0017760 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/data/keys/ 0000775 0000000 0000000 00000000000 14726101056 0017463 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/testing/data/keys/ark_private_key.pem 0000664 0000000 0000000 00000006310 14726101056 0023345 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
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
MkegxleNCeJpIn1Wsj7a0gVy2ub1Lg==
-----END PRIVATE KEY-----
go-sev-guest-0.12.1/testing/data/keys/ask_private_key.pem 0000664 0000000 0000000 00000006310 14726101056 0023346 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
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
MDTH4Mh5L36CtDc2VZJhlsKpBIwskqM=
-----END PRIVATE KEY-----
go-sev-guest-0.12.1/testing/data/keys/asvk_private_key.pem 0000664 0000000 0000000 00000006310 14726101056 0023534 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
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
nbAMwwicy/ojYIpDbKmVFEnX3Zy4TA==
-----END PRIVATE KEY-----
go-sev-guest-0.12.1/testing/data/keys/vcek_private_key.pem 0000664 0000000 0000000 00000000462 14726101056 0023522 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAnWpLdR3Yr2nV7M1dy
HSCUp2ZMlIP50hdvOkkPNTEiuLYxhQkxXS/WO0/nfdGdQO2hZANiAAQ2I63wh+Ud
nxnPb6Izu8aAH0qFFrF04n0YK1PjLl1lZgdsZQZWrtOnSr7dQtE300gtjBiBlGGB
0k2MuCt+OsrYgXeN+yTWfe52V28BvacmJedYbmTViN0hXhm6+1h1AU4=
-----END PRIVATE KEY-----
go-sev-guest-0.12.1/testing/data/keys/vlek_private_key.pem 0000664 0000000 0000000 00000000462 14726101056 0023533 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBunPPtTnRUcgPCyam+
Hrd6HkvDkDlciDPB04JX/V5e4LMd9Tp1NdNXiXQ6vA/hal2hZANiAAQsWkZc2y+J
/oNbImA+tjkuORiCNzo84vz5zoIg3vT49hpTtP46r+30dVZVbN/GYBAqhi680De5
qXjOW5ZdvtXhEVi0G6AYWJuh/h/bAQyNeMQACFoPaYkwIlcGXHMnD3g=
-----END PRIVATE KEY-----
go-sev-guest-0.12.1/testing/fake_certs.go 0000664 0000000 0000000 00000043735 14726101056 0020250 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/fake_certs_test.go 0000664 0000000 0000000 00000006133 14726101056 0021276 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/fakekds.go 0000664 0000000 0000000 00000015742 14726101056 0017547 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/match.go 0000664 0000000 0000000 00000001506 14726101056 0017224 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/mocks.go 0000664 0000000 0000000 00000017517 14726101056 0017255 0 ustar 00root root 0000000 0000000 // 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.12.1/testing/test_cases.go 0000664 0000000 0000000 00000050667 14726101056 0020301 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/ 0000775 0000000 0000000 00000000000 14726101056 0015262 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/attest/ 0000775 0000000 0000000 00000000000 14726101056 0016566 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/attest/README.md 0000664 0000000 0000000 00000006036 14726101056 0020052 0 ustar 00root root 0000000 0000000 # `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.12.1/tools/attest/attest.go 0000664 0000000 0000000 00000015077 14726101056 0020433 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/check/ 0000775 0000000 0000000 00000000000 14726101056 0016337 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/check/README.md 0000664 0000000 0000000 00000012263 14726101056 0017622 0 ustar 00root root 0000000 0000000 # `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.12.1/tools/check/check.go 0000664 0000000 0000000 00000043653 14726101056 0017756 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/check/check_test.go 0000664 0000000 0000000 00000034057 14726101056 0021013 0 ustar 00root root 0000000 0000000 package 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.12.1/tools/lib/ 0000775 0000000 0000000 00000000000 14726101056 0016030 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/lib/cmdline/ 0000775 0000000 0000000 00000000000 14726101056 0017443 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/lib/cmdline/cmdline.go 0000664 0000000 0000000 00000010353 14726101056 0021407 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/lib/cmdline/cmdline_test.go 0000664 0000000 0000000 00000012575 14726101056 0022456 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/lib/report/ 0000775 0000000 0000000 00000000000 14726101056 0017343 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/lib/report/report.go 0000664 0000000 0000000 00000010760 14726101056 0021211 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/lib/report/report_test.go 0000664 0000000 0000000 00000015227 14726101056 0022253 0 ustar 00root root 0000000 0000000 // 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.12.1/tools/show/ 0000775 0000000 0000000 00000000000 14726101056 0016242 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/tools/show/main.go 0000664 0000000 0000000 00000003355 14726101056 0017523 0 ustar 00root root 0000000 0000000 // 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.12.1/validate/ 0000775 0000000 0000000 00000000000 14726101056 0015713 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/validate/validate.go 0000664 0000000 0000000 00000067363 14726101056 0020052 0 ustar 00root root 0000000 0000000 // 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.12.1/validate/validate_test.go 0000664 0000000 0000000 00000045006 14726101056 0021077 0 ustar 00root root 0000000 0000000 // 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.12.1/verify/ 0000775 0000000 0000000 00000000000 14726101056 0015426 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/verify/testdata/ 0000775 0000000 0000000 00000000000 14726101056 0017237 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/verify/testdata/attestation.bin 0000664 0000000 0000000 00000002240 14726101056 0022266 0 ustar 00root root 0000000 0000000 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] D1 1 D OŽ‹Z¸øùiÊO'¶»¦_ªS®rök‰8t¼åÖ-;º»2ÂÉ¥ÒKP¢2™œÈ! æ‰$k •f¶¶ù0¡_4½e~ôGøvB‹×é
Û,É1ìö”˜U]à go-sev-guest-0.12.1/verify/testdata/milan.testcer 0000664 0000000 0000000 00000010772 14726101056 0021741 0 ustar 00root root 0000000 0000000 -----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.12.1/verify/testdata/milanvlek.testcer 0000664 0000000 0000000 00000011003 14726101056 0022607 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIGjzCCBD6gAwIBAgIDAQEBMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC
BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS
BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg
Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp
Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjIxMTE2MjI0NTI0WhcNNDcxMTE2
MjI0NTI0WjCBgDEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQw
EgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFu
Y2VkIE1pY3JvIERldmljZXMxFzAVBgNVBAMMDlNFVi1WTEVLLU1pbGFuMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1EUWkz5FTPz+uWT2hCEyisam8FRu
XZAmS3l+rXgSCeS1Q0+1olcnFSJpiwfssfhoutJqePyicu+OhkX131PMeO/VOtH3
upK4YNJmq36IJp7ZWIm5nK2fJNkYEHW0m/NXcIA9U2iHl5bAQ5cbGp97/FaOJ4Vm
GoTMV658Yox/plFmZRFfRcsw2hyNhqUl1gzdpnIIgPkygUovFEgaa0IVSgGLHQhZ
QiebNLLSVWRVReve0t94zlRIRRdrz84cckP9H9DTAUMyQaxSZbPINKbV6TPmtrwA
V9UP1Qq418xn9I+C0SsWutP/5S1OiL8OTzQ4CvgbHOfd2F3yVv4xDBza4SelF2ig
oDf+BF4XI/IIHJL2N5uKy3+gkSB2Xl6prohgVmqRFvBW9OTCEa32WhXu0t1Z1abE
KDZ3LpZt9/Crg6zyPpXDLR/tLHHpSaPRj7CTzHieKMTz+Q6RrCCQcHGfaAD/ETNY
56aHvNJRZgbzXDUJvnLr3dYyOvvn/DtKhCSimJynn7Len4ArDVQVwXRPe3hR/asC
E2CajT7kGC1AOtUzQuIKZS2D0Qk74g297JhLHpEBlQiyjRJ+LCWZNx9uJcixGyza
v6fiOWx4U8uWhRzHs8nvDAdcS4LW31tPlA9BeOK/BGimQTu7hM5MDFZL0C9dWK5p
uCUJex6I2vSqvycCAwEAAaOBozCBoDAdBgNVHQ4EFgQUNuJXE6qi45/CgqkKRPtV
LObC7pEwHwYDVR0jBBgwFoAUhawa0UP3yKxV1MUdQUir1XhK1FMwEgYDVR0TAQH/
BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQQwOgYDVR0fBDMwMTAvoC2gK4YpaHR0
cHM6Ly9rZHNpbnRmLmFtZC5jb20vdmxlay92MS9NaWxhbi9jcmwwRgYJKoZIhvcN
AQEKMDmgDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQME
AgIFAKIDAgEwowMCAQEDggIBAI7ayEXDNj1rCVnjQFb6L91NNOmEIOmi6XtopAqr
8fj7wqXap1MY82Y0AIi1K9R7C7G1sCmY8QyEyX0zqHsoNbU2IMcSdZrIp8neT8af
v8tPt7qoW3hZ+QQRMtgVkVVrjJZelvlB74xr5ifDcDiBd2vu/C9IqoQS4pVBKNSF
pofzjtYKvebBBBXxeM2b901UxNgVjCY26TtHEWN9cA6cDVqDDCCL6uOeR9UOvKDS
SqlM6nXldSj7bgK7Wh9M9587IwRvNZluXc1CDiKMZybLdSKOlyMJH9ss1GPn0eBV
EhVjf/gttn7HrcQ9xJZVXyDtL3tkGzemrPK14NOYzmph6xr1iiedAzOVpNdPiEXn
2lvas0P4TD9UgBh0Y7xyf2yENHiSgJT4T8Iktm/TSzuh4vqkQ72A1HdNTGjoZcfz
KCsQJ/YuFICeaNxw5cIAGBK/o+6Ek32NPv5XtixNOhEx7GsaVRG05bq5oTt14b4h
KYhqV1CDrX5hiVRpFFDs/sAGfgTzLdiGXLcvYAUz1tCKIT/eQS9c4/yitn4F3mCP
d4uQB+fggMtK0qPRthpFtc2SqVCTvHnhxyXqo7GpXMsssgLgKNwaFPe2+Ld5OwPR
6Pokji9h55m05Dxob8XtD4gW6oFLo9Icg7XqdOr9Iip5RBIPxy7rKk/ReqGs9KH7
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.12.1/verify/testdata/testdata.go 0000664 0000000 0000000 00000003000 14726101056 0021370 0 ustar 00root root 0000000 0000000 // 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.12.1/verify/testdata/vcek.testcer 0000664 0000000 0000000 00000002520 14726101056 0021561 0 ustar 00root root 0000000 0000000 0‚L0‚û 0F *†H†÷
09 0
`†He ¡0 *†H†÷
0
`†He ¢0£0{10UEngineering10 UUS10USanta Clara10 UCA10U
Advanced Micro Devices10U SEV-Milan0
220924005528Z
290924005528Z0z10UEngineering10 UUS10USanta Clara10 UCA10U
Advanced Micro Devices10USEV-VCEK0v0*†HÎ=+ "b HìŸ6.íî^¡vY9Ó
ÆÀVÈ!šÞqÅ…Òâw<ÞC0²
j¸ßŽáØ\ò“£…O` ô…,̓ˆ'r«þ‚ÿÌpúŠcû”õ2È@òç¨5`|Ç%¿[ˆOþÉN>ù£‚0‚0 +œx 0 +œx
Milan-B00
+œx0
+œx 0
+œx 0
+œx 0
+œx 0
+œx 0
+œ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$rqBÈÍ%Èó!«Oã€n9ÛçxÚu™%˜‹âÄcÔPß&%JaúÓ+ø†¥[ go-sev-guest-0.12.1/verify/trust/ 0000775 0000000 0000000 00000000000 14726101056 0016607 5 ustar 00root root 0000000 0000000 go-sev-guest-0.12.1/verify/trust/ask_ark_genoa.pem 0000664 0000000 0000000 00000010772 14726101056 0022105 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
/wMz1R1BHg==
-----END CERTIFICATE-----
go-sev-guest-0.12.1/verify/trust/ask_ark_genoa_vlek.pem 0000664 0000000 0000000 00000011003 14726101056 0023112 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
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
2cko
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
/wMz1R1BHg==
-----END CERTIFICATE-----
go-sev-guest-0.12.1/verify/trust/ask_ark_milan.pem 0000664 0000000 0000000 00000010772 14726101056 0022114 0 ustar 00root root 0000000 0000000 -----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.12.1/verify/trust/ask_ark_milan_vlek.pem 0000664 0000000 0000000 00000011003 14726101056 0023121 0 ustar 00root root 0000000 0000000 -----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.12.1/verify/trust/ask_ark_turin_vcek.pem 0000664 0000000 0000000 00000011104 14726101056 0023153 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIGiTCCBDigAwIBAgIDAwABMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC
BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS
BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg
Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp
Y2VzMRIwEAYDVQQDDAlBUkstVHVyaW4wHhcNMjMwNTE1MjAyNTIxWhcNNDgwNTE1
MjAyNTIxWjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS
BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j
ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJU0VWLVR1cmluMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAnvg5Grv2Emd9lAhKdO64RXU3UESb6JTm0Hhz
evx1PyxinxYqJL329qTJM0XmdozLYb7rsHxgM5I2pU18M8gect2pN/YB2LQ1/bIq
37TPDbg7ym0MN6KkZ6aERxAX0voYtdDyNxjDAUjpRpCe1FccAev/Es2n/Fz1G1Tm
C2XepTQqaKpmt6mnDWSCHCVsQoY0gSibeaG6doM6OiNUCbKXaC7KHH5b/96BD1DJ
84M+JHqPClFhHqUJwzKF5Qxj4wgWAZzK8UPhiNGjrF6+TBdlFGdSzEqw1jOrCTHd
uYyLK+5OQ3OIw4S+vZeOVoxJajTIWdsqYP2DLc0HkL0qWOumEOrrc2/4DeETShB0
MyIpH05kSalyQN2eN5P6ptOB84hddCdbJPEepnD+FqQap1ukw3K8uBcgeBSAF23r
6UtT8Uc5h7MsWX3MoZiEHcSkDQQ8IedTk7CLjsK6S7b/lfKqfYiRhKgGkRvsEd/M
DNcumHZKIgzasJwgagzSggiUo9jXp3EWm84fqyxNXzSutPB7qD5P/ULAB+q9Qgvr
zC8XneaLP0MNrHhM80UejmsBTIktMvFoWVIelYDLdcoi0eMD5DRccfsgrYaY6h/+
/qf9tgg+mX09UJpuSPRF38oyqnNNFMl5v/tWLgUsChPU6NCQC17Qaqr8mu2ynyyu
HEs5JVUCAwEAAaOBozCBoDAdBgNVHQ4EFgQUbYJXt6v2sMgUALjxD0WvG9aq628w
HwYDVR0jBBgwFoAUZKBfceMMCmTYO3XlAVmeK+4GA0QwEgYDVR0TAQH/BAgwBgEB
/wIBADAOBgNVHQ8BAf8EBAMCAQQwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cHM6Ly9r
ZHNpbnRmLmFtZC5jb20vdmNlay92MS9UdXJpbi9jcmwwRgYJKoZIhvcNAQEKMDmg
DzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIFAKID
AgEwowMCAQEDggIBAAXWJ3DPahralt5kXLPMm9oKlFRqeU3HcS7kA+VBlBA1lQRU
hXkbXnTvW1GZcgdZvNCB/VlET61KbCzoFIhPIESVjjb/xWX2kg3X0HHmh1EtCDbH
aUFM5rq6l+S1h7qOauRZebvrwApDzAANvW0LTHRumfGm/kqh9NDtVCIWPUZ1VQIg
Gx1T3dwmgOK8ncT1J3W5xIyS0Xu3KC6w7oBlq8G2pPgTcCBJ4JBCTXCEXiAAGaTR
/TJIaSzoZFLhxYhCMjP8WQGToPGDK2i/lZhkcGHnJOQ+lgrXfpLGqBtLlS3QODyV
P0MomczG4dqw3THP3Y8Aq9c2KE7SylAKsS/bBKCqkj4OrABkDSkMQEz3BBoFD63a
D5ZG/Qiz+tmhnptyPVcweC9uJlSWYm25KiV4lT52uBjxatDZKQcrpdgcU8+ozzKU
8ICnZPOwfWeyuNMq/juyd/rzg5IePyyvt+13aJ5MlZBXZxJKoxCYIMKUwZigf0Xs
BteT8gw10/xk5smIFIB2ERtTQPMuTENgrPTUjOeiqmBg663c2dLVol+MDiT4ltqf
Em4Kl/cc4f+H6bEwhj1QKAN2ipRf+mP0NfzJb+6ZHNsOvyq/WByYpLXV9JJoiDW/
8RZwPU/Mn7IuQBauCy78G7FS0ta3q1et74faYBBgeJ6awEasa25CvmsmlU0R
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
zpacMwFusA==
-----END CERTIFICATE-----
go-sev-guest-0.12.1/verify/trust/ask_ark_turin_vlek.pem 0000664 0000000 0000000 00000011120 14726101056 0023162 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
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
SUxr
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
zpacMwFusA==
-----END CERTIFICATE-----
go-sev-guest-0.12.1/verify/trust/trust.go 0000664 0000000 0000000 00000032163 14726101056 0020324 0 ustar 00root root 0000000 0000000 // 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.12.1/verify/trust/trust_test.go 0000664 0000000 0000000 00000006207 14726101056 0021363 0 ustar 00root root 0000000 0000000 // 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.12.1/verify/verify.go 0000664 0000000 0000000 00000100326 14726101056 0017263 0 ustar 00root root 0000000 0000000 // 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 uints", "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) {
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.12.1/verify/verify_test.go 0000664 0000000 0000000 00000066176 14726101056 0020340 0 ustar 00root root 0000000 0000000 // 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")
}
}