pax_global_header00006660000000000000000000000064145637137630014531gustar00rootroot0000000000000052 comment=43efd1ff6ae89a081dbb3cccb6f1fbe6541a0ad6 go-tdx-guest-0.3.1/000077500000000000000000000000001456371376300140615ustar00rootroot00000000000000go-tdx-guest-0.3.1/.github/000077500000000000000000000000001456371376300154215ustar00rootroot00000000000000go-tdx-guest-0.3.1/.github/workflows/000077500000000000000000000000001456371376300174565ustar00rootroot00000000000000go-tdx-guest-0.3.1/.github/workflows/CI.yml000066400000000000000000000056441456371376300205050ustar00rootroot00000000000000# # 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. # name: CI 'on': push: tags: - v* branches: - main pull_request: null jobs: build: strategy: matrix: go-version: - 1.19.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@v1 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' version: 3.12.4 - name: Install protoc-gen-go run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 - 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 ./... 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.3.0 with: version: latest working-directory: ./ args: > -D errcheck -E stylecheck -E goimports -E misspell -E revive -E gofmt -E goimports --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-tdx-guest-0.3.1/.github/workflows/release.yml000066400000000000000000000022401456371376300216170ustar00rootroot00000000000000name: release 'on': push: branches: - main tags: - v* pull_request: null 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-tdx-guest-0.3.1/.goreleaser.yml000066400000000000000000000021211456371376300170060ustar00rootroot00000000000000builds: - 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=cnqojgo-tdx-guest-0.3.1/CONTRIBUTING.md000066400000000000000000000021151456371376300163110ustar00rootroot00000000000000# 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-tdx-guest-0.3.1/LICENSE000066400000000000000000000261351456371376300150750ustar00rootroot00000000000000 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-tdx-guest-0.3.1/README.md000066400000000000000000000123411456371376300153410ustar00rootroot00000000000000# TDX Guest This project offers libraries for a simple wrapper around quote providing tools such as the `go-configfs-tsm` library, or the `/dev/tdx_guest` device in Linux, as well as a library for attestation verification of fundamental components of an attestation quote. This project is split into two complementary roles. The first role is producing an attestation quote, and the second is checking an attestation quote. The `client` library produces quote, the `verify` library verifies quote's signatures and certificates. ## `client` This library should be used within the confidential workload to collect an attestation quote along with requisite certificates. Your main interactions with it will be to first get the quote provider, or open the device, then get an attestation quote with your provided 64 bytes of user data (typically a nonce), and then close the device. For convenience, the attestation with its associated certificates can be collected in a wire-transmittable protocol buffer format. ### `func GetQuoteProvider() (*LinuxConfigFsQuoteProvider, error)` This function creates an instance of a quote provider which uses the go-configfs-tsm library to fetch attestation quotes via ConfigFS. ### `func OpenDevice() (*LinuxDevice, error)` This function creates a file descriptor to the `/dev/tdx_guest` device and returns an object that has methods encapsulating commands to the device. When done, remember to `Close()` the device. Note:- The Device interface is deprecated, and use of quote provider interface is recommended for fetching attestation quote. ### `func GetQuote(quoteProvider any, reportData [64]byte) (any, error)` This function takes an object implementing either the `QuoteProvider` interface (e.g. `LinuxConfigFsQuoteProvider`), or the `Device` interface (e.g., a `LinuxDevice`) along with report data which typically consists of a nonce value. It returns the protocol buffer representation of the attestation quote. You can use `GetRawQuote` to get the TDX Quote in byte array format. ### `func (d Device) Close() error` Closes the device. ## `verify` This library will check the signature, certificate chain and basic well-formedness properties of an attestation quote. The requirements for quote well-formedness come from the [Intel TDX specification](https://cdrdv2.intel.com/v1/dl/getContent/733568), and the requirements for certificate well-formedness come from the [Intel PCK Certificate specification](https://api.trustedservices.intel.com/documents/Intel_SGX_PCK_Certificate_CRL_Spec-1.5.pdf). The presence of the PCK Certificate Chain within the input attestation quote is expected. ### `func TdxQuote(quote *pb.QuoteV4, options *Options) error` This function verifies that the attestation has a valid signature and certificate chain. It provides an optional verification against the collateral obtained from the Intel PCS API and also offers an optional check against the certificate revocation list (CRL). By default, the option to verify against collaterals and the certificate revocation list(CRL) is disabled. The verification using collaterals is based on [Intel PCS API specification](https://api.portal.trustedservices.intel.com/provisioning-certification) documentation. Example expected invocation: ``` verify.TdxQuote(myAttestation, verify.Options()) ``` #### `Options` type This type contains five fields: * `GetCollateral bool`: if true, then `TdxQuote` will download the collateral from Intel PCS API service and check against collateral obtained. Must be `true` if `CheckRevocations` is true. * `CheckRevocations bool`: if true, then `TdxQuote` will download the certificate revocation list (CRL) from Intel PCS API service and check for revocations. * `Getter HTTPSGetter`: if `nil`, uses `DefaultHTTPSGetter()`. The `HTTPSGetter` interface consists of a single method `Get(url string) (map[string][]string, []byte, error)` that should return the headers and body of the HTTPS response. * `Now time.Time`: if `nil`, uses `time.Now()`. It is the time at which to verify the validity of certificates and collaterals. * `TrustedRoots *x509.CertPool`: if `nil`, uses the library's embedded certificate. Certificate chain verification is performed using trusted roots. ## License go-tdx-guest is released under the Apache 2.0 license. ``` 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. ``` ## Links * [Intel TDX specification](https://cdrdv2.intel.com/v1/dl/getContent/733568) * [Intel PCK Certificate specification](https://api.trustedservices.intel.com/documents/Intel_SGX_PCK_Certificate_CRL_Spec-1.5.pdf) * [Intel PCS API specification](https://api.portal.trustedservices.intel.com/provisioning-certification) ## Disclaimers This is not an officially supported Google product.go-tdx-guest-0.3.1/abi/000077500000000000000000000000001456371376300146145ustar00rootroot00000000000000go-tdx-guest-0.3.1/abi/abi.go000066400000000000000000001140101456371376300156730ustar00rootroot00000000000000// 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 abi provides Go representations and conversions for TDX attestation // data structures package abi import ( "encoding/binary" "errors" "fmt" "math/big" pb "github.com/google/go-tdx-guest/proto/tdx" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" ) const ( // QuoteMinSize is the minimum specified size of TDX generated quote QuoteMinSize = 0x3FC // QuoteVersion currently in support QuoteVersion = 4 // AttestationKeyType supported value AttestationKeyType = 2 // (ECDSA-256-with-P-256 curve) // TeeTDX for Attestation TeeTDX = 0x00000081 // TeeTcbSvnSize is the size of TEE_TCB_SVN field in TdQuoteBody TeeTcbSvnSize = 0x10 // MrSeamSize is the size of MR_SEAM field in TdQuoteBody MrSeamSize = 0x30 // TdAttributesSize is the size of TD_ATTRIBUTES field in TdQuoteBody TdAttributesSize = 0x08 // XfamSize is the size of XFAM field in TdQuoteBody XfamSize = 0x08 // MrTdSize is the size of MR_TD field in TdQuoteBody MrTdSize = 0x30 // MrConfigIDSize is the size of MR_CONFIG_ID field in TdQuoteBody MrConfigIDSize = 0x30 // MrOwnerSize is the size of MR_OWNER field in TdQuoteBody MrOwnerSize = 0x30 // MrOwnerConfigSize is the size of MR_OWNER_CONFIG field in TdQuoteBody MrOwnerConfigSize = 0x30 // RtmrSize is the size of Runtime extendable measurement register RtmrSize = 0x30 // ReportDataSize is the size of ReportData field in TdQuoteBody ReportDataSize = 0x40 // QeVendorIDSize is the size of QeVendorID field in Header QeVendorIDSize = 0x10 userDataSize = 0x14 qeReportCertificationDataType = 0x6 pckReportCertificationDataType = 0x5 qeReportSize = 0x180 headerSize = 0x30 tdQuoteBodySize = 0x248 qeSvnSize = 0x2 pceSvnSize = 0x2 mrSignerSeamSize = 0x30 seamAttributesSize = 0x08 cpuSvnSize = 0x10 reserved1Size = 0x1C attributesSize = 0x10 mrEnclaveSize = 0x20 reserved2Size = 0x20 mrSignerSize = 0x20 reserved3Size = 0x60 reserved4Size = 0x3C signatureSize = 0x40 attestationKeySize = 0x40 pckCertificateChainKnownSize = 0x06 qeAuthDataKnownSize = 0x02 certificationDataKnownSize = 0x06 quoteV4AuthDataKnownSize = 0x80 quoteHeaderStart = 0x00 quoteHeaderEnd = 0x30 quoteBodyStart = 0x30 quoteBodyEnd = 0x278 quoteSignedDataSizeStart = 0x278 quoteSignedDataSizeEnd = 0x27C quoteSignedDataStart = quoteSignedDataSizeEnd headerVersionStart = 0x00 headerVersionEnd = 0x02 headerAttestationKeyTypeStart = headerVersionEnd headerAttestationKeyTypeEnd = 0x04 headerTeeTypeStart = headerAttestationKeyTypeEnd headerTeeTypeEnd = 0x08 headerPceSvnStart = headerTeeTypeEnd headerPceSvnEnd = 0xA headerQeSvnStart = headerPceSvnEnd headerQeSvnEnd = 0xC headerQeVendorIDStart = headerQeSvnEnd headerQeVendorIDEnd = 0x1C headerUserDataStart = headerQeVendorIDEnd headerUserDataEnd = 0x30 intelQuoteV4Version = 4 tdTeeTcbSvnStart = 0x00 tdTeeTcbSvnEnd = 0x10 tdMrSeamStart = tdTeeTcbSvnEnd tdMrSeamEnd = 0x40 tdMrSignerSeamStart = tdMrSeamEnd tdMrSignerSeamEnd = 0x70 tdSeamAttributesStart = tdMrSignerSeamEnd tdSeamAttributesEnd = 0x78 tdAttributesStart = tdSeamAttributesEnd tdAttributesEnd = 0x80 tdXfamStart = tdAttributesEnd tdXfamEnd = 0x88 tdMrTdStart = tdXfamEnd tdMrTdEnd = 0xB8 tdMrConfigIDStart = tdMrTdEnd tdMrConfigIDEnd = 0xE8 tdMrOwnerStart = tdMrConfigIDEnd tdMrOwnerEnd = 0x118 tdMrOwnerConfigStart = tdMrOwnerEnd tdMrOwnerConfigEnd = 0x148 tdRtmrsStart = tdMrOwnerConfigEnd tdRtmrsEnd = 0x208 tdReportDataStart = tdRtmrsEnd tdReportDataEnd = 0x248 signedDataSignatureStart = 0x00 signedDataSignatureEnd = 0x40 signedDataAttestationKeyStart = signedDataSignatureEnd signedDataAttestationKeyEnd = 0x80 signedDataCertificationDataStart = signedDataAttestationKeyEnd certificateDataTypeStart = 0x00 certificateDataTypeEnd = 0x02 certificateSizeStart = certificateDataTypeEnd certificateSizeEnd = 0x06 certificateDataStart = certificateSizeEnd enclaveReportStart = 0x00 enclaveReportEnd = 0x180 qeReportCertificationDataSignatureStart = enclaveReportEnd qeReportCertificationDataSignatureEnd = 0x1C0 qeReportCertificationDataAuthDataStart = qeReportCertificationDataSignatureEnd qeCPUSvnStart = 0x00 qeCPUSvnEnd = 0x10 qeMiscSelectStart = qeCPUSvnEnd qeMiscSelectEnd = 0x14 qeReserved1Start = qeMiscSelectEnd qeReserved1End = 0x30 qeAttributesStart = qeReserved1End qeAttributesEnd = 0x40 qeMrEnclaveStart = qeAttributesEnd qeMrEnclaveEnd = 0x60 qeReserved2Start = qeMrEnclaveEnd qeReserved2End = 0x80 qeMrSignerStart = qeReserved2End qeMrSignerEnd = 0xA0 qeReserved3Start = qeMrSignerEnd qeReserved3End = 0x100 qeIsvProdIDStart = qeReserved3End qeIsvProdIDEnd = 0x102 qeIsvSvnStart = qeIsvProdIDEnd qeIsvSvnEnd = 0x104 qeReserved4Start = qeIsvSvnEnd qeReserved4End = 0x140 qeReportDataStart = qeReserved4End qeReportDataEnd = 0x180 authDataParsedDataSizeStart = 0x00 authDataParsedDataSizeEnd = 0x02 authDataStart = authDataParsedDataSizeEnd pckCertChainCertificationDataTypeStart = 0x00 pckCertChainCertificationDataTypeEnd = 0x02 pckCertChainSizeStart = pckCertChainCertificationDataTypeEnd pckCertChainSizeEnd = 0x06 pckCertChainDataStart = pckCertChainSizeEnd rtmrsCount = 4 ) var ( // ErrQuoteNil error returned when Quote is nil ErrQuoteNil = errors.New("quote is nil") // ErrQuoteV4Nil error returned when QuoteV4 is nil ErrQuoteV4Nil = errors.New("QuoteV4 is nil") // ErrQuoteV4AuthDataNil error returned when QuoteV4 Auth Data is nil ErrQuoteV4AuthDataNil = errors.New("QuoteV4 authData is nil") // ErrCertificationDataNil error returned when Certification Data is nil ErrCertificationDataNil = errors.New("certification data is nil") // ErrQeReportCertificationDataNil error returned when QE report certification data is nil ErrQeReportCertificationDataNil = errors.New("QE Report certification data is nil") // ErrQeAuthDataNil error returned when QE Auth Data is nil ErrQeAuthDataNil = errors.New("QE AuthData is nil") // ErrQeReportNil error returned when QE Report is nil ErrQeReportNil = errors.New("QE Report is nil") // ErrPckCertChainNil error returned when PCK Certificate Chain is nil ErrPckCertChainNil = errors.New("PCK certificate chain is nil") // ErrTDQuoteBodyNil error returned when TD quote body is nil ErrTDQuoteBodyNil = errors.New("TD quote body is nil") // ErrTeeType error returned when TEE type is not TDX ErrTeeType = errors.New("TEE type is not TDX") // ErrAttestationKeyType error returned when attestation key is not of expected type ErrAttestationKeyType = errors.New("attestation key type not supported") // ErrHeaderNil error returned when header is nil ErrHeaderNil = errors.New("header is nil") ) func clone(b []byte) []byte { result := make([]byte, len(b)) copy(result, b) return result } // determineQuoteFormat returns the quote format version from the header. func determineQuoteFormat(b []uint8) (uint32, error) { if len(b) < headerVersionEnd { return 0, fmt.Errorf("unable to determine quote format since bytes length is less than %d bytes", headerVersionEnd) } data := clone(b) header := &pb.Header{} header.Version = uint32(binary.LittleEndian.Uint16(data[headerVersionStart:headerVersionEnd])) return header.Version, nil } // QuoteToProto creates a Quote from the Intel's attestation quote byte array in Intel's ABI format. // Supported quote formats - QuoteV4. func QuoteToProto(b []uint8) (any, error) { quoteFormat, err := determineQuoteFormat(b) if err != nil { return nil, err } switch quoteFormat { case intelQuoteV4Version: return quoteToProtoV4(b) default: return nil, fmt.Errorf("quote format not supported") } } // quoteToProtoV4 creates a pb.QuoteV4 from the Intel's attestation quote byte array in Intel's ABI format. func quoteToProtoV4(b []uint8) (*pb.QuoteV4, error) { data := clone(b) // Created an independent copy to make the interface less error-prone if len(data) < QuoteMinSize { return nil, fmt.Errorf("raw quote size is 0x%x, a TDX quote should have size a minimum size of 0x%x", len(data), QuoteMinSize) } quote := &pb.QuoteV4{} header, err := headerToProto(data[quoteHeaderStart:quoteHeaderEnd]) if err != nil { return nil, err } tdQuoteBody, err := tdQuoteBodyToProto(data[quoteBodyStart:quoteBodyEnd]) if err != nil { return nil, err } quote.SignedDataSize = binary.LittleEndian.Uint32(data[quoteSignedDataSizeStart:quoteSignedDataSizeEnd]) additionalData := data[quoteSignedDataStart:] if uint32(len(additionalData)) < quote.GetSignedDataSize() { return nil, fmt.Errorf("size of signed data is 0x%x. Expected minimum size of 0x%x", len(additionalData), quote.GetSignedDataSize()) } quoteSignedDataEnd := quoteSignedDataStart + quote.GetSignedDataSize() rawSignedData := data[quoteSignedDataStart:quoteSignedDataEnd] extraBytes := data[quoteSignedDataEnd:] signedData, err := signedDataToProto(rawSignedData) if err != nil { return nil, err } quote.Header = header quote.TdQuoteBody = tdQuoteBody quote.SignedData = signedData if len(extraBytes) > 0 { quote.ExtraBytes = extraBytes } if err := CheckQuoteV4(quote); err != nil { return nil, fmt.Errorf("parsing QuoteV4 failed: %v", err) } return quote, nil } func headerToProto(b []uint8) (*pb.Header, error) { data := clone(b) // Created an independent copy to make the interface less error-prone header := &pb.Header{} header.Version = uint32(binary.LittleEndian.Uint16(data[headerVersionStart:headerVersionEnd])) header.AttestationKeyType = uint32(binary.LittleEndian.Uint16(data[headerAttestationKeyTypeStart:headerAttestationKeyTypeEnd])) header.TeeType = binary.LittleEndian.Uint32(data[headerTeeTypeStart:headerTeeTypeEnd]) header.PceSvn = data[headerPceSvnStart:headerPceSvnEnd] header.QeSvn = data[headerQeSvnStart:headerQeSvnEnd] header.QeVendorId = data[headerQeVendorIDStart:headerQeVendorIDEnd] header.UserData = data[headerUserDataStart:headerUserDataEnd] if err := checkHeader(header); err != nil { return nil, fmt.Errorf("parsing header failed: %v", err) } return header, nil } func tdQuoteBodyToProto(b []uint8) (*pb.TDQuoteBody, error) { data := clone(b) // Created an independent copy to make the interface less error-prone report := &pb.TDQuoteBody{} report.TeeTcbSvn = data[tdTeeTcbSvnStart:tdTeeTcbSvnEnd] report.MrSeam = data[tdMrSeamStart:tdMrSeamEnd] report.MrSignerSeam = data[tdMrSignerSeamStart:tdMrSignerSeamEnd] report.SeamAttributes = data[tdSeamAttributesStart:tdSeamAttributesEnd] report.TdAttributes = data[tdAttributesStart:tdAttributesEnd] report.Xfam = data[tdXfamStart:tdXfamEnd] report.MrTd = data[tdMrTdStart:tdMrTdEnd] report.MrConfigId = data[tdMrConfigIDStart:tdMrConfigIDEnd] report.MrOwner = data[tdMrOwnerStart:tdMrOwnerEnd] report.MrOwnerConfig = data[tdMrOwnerConfigStart:tdMrOwnerConfigEnd] report.ReportData = data[tdReportDataStart:tdReportDataEnd] rtmrsStart := tdRtmrsStart for i := 0; i < rtmrsCount; i++ { rtmrsEnd := rtmrsStart + RtmrSize arr := data[rtmrsStart:rtmrsEnd] report.Rtmrs = append(report.Rtmrs, arr) rtmrsStart += RtmrSize } if err := checkTDQuoteBody(report); err != nil { return nil, fmt.Errorf("parsing TD Quote Body failed: %v", err) } return report, nil } func signedDataToProto(b []uint8) (*pb.Ecdsa256BitQuoteV4AuthData, error) { data := clone(b) // Created an independent copy to make the interface less error-prone signedData := &pb.Ecdsa256BitQuoteV4AuthData{} signedData.Signature = data[signedDataSignatureStart:signedDataSignatureEnd] signedData.EcdsaAttestationKey = data[signedDataAttestationKeyStart:signedDataAttestationKeyEnd] certificationData, err := certificationDataToProto(data[signedDataCertificationDataStart:]) if err != nil { return nil, err } signedData.CertificationData = certificationData if err := checkEcdsa256BitQuoteV4AuthData(signedData); err != nil { return nil, fmt.Errorf("parsing QuoteV4 AuthData failed: %v", err) } return signedData, nil } func certificationDataToProto(b []uint8) (*pb.CertificationData, error) { data := clone(b) // Created an independent copy to make the interface less error-prone certification := &pb.CertificationData{} certification.CertificateDataType = uint32(binary.LittleEndian.Uint16(data[certificateDataTypeStart:certificateDataTypeEnd])) certification.Size = binary.LittleEndian.Uint32(data[certificateSizeStart:certificateSizeEnd]) rawCertificateData := data[certificateDataStart:] if uint32(len(rawCertificateData)) != certification.GetSize() { return nil, fmt.Errorf("size of certificate data is 0x%x. Expected size 0x%x", len(rawCertificateData), certification.GetSize()) } qeReportCertificationData, err := qeReportCertificationDataToProto(rawCertificateData) if err != nil { return nil, err } certification.QeReportCertificationData = qeReportCertificationData if err := checkCertificationData(certification); err != nil { return nil, fmt.Errorf("parsing certification data failed: %v", err) } return certification, nil } func qeReportCertificationDataToProto(b []uint8) (*pb.QEReportCertificationData, error) { data := clone(b) // Created an independent copy to make the interface less error-prone qeReportCertificationData := &pb.QEReportCertificationData{} enclaveReport, err := enclaveReportToProto(data[enclaveReportStart:enclaveReportEnd]) if err != nil { return nil, err } qeReportCertificationData.QeReport = enclaveReport qeReportCertificationData.QeReportSignature = data[qeReportCertificationDataSignatureStart:qeReportCertificationDataSignatureEnd] authData, authDataSize, err := qeAuthDataToProto(data[qeReportCertificationDataAuthDataStart:]) if err != nil { return nil, err } qeReportCertificationData.QeAuthData = authData pckCertificateStart := qeReportCertificationDataAuthDataStart + authDataSize pckCertificateChain, err := pckCertificateChainToProto(data[pckCertificateStart:]) if err != nil { return nil, err } qeReportCertificationData.PckCertificateChainData = pckCertificateChain if err := checkQeReportCertificationData(qeReportCertificationData); err != nil { return nil, fmt.Errorf("parsing QE Report Certification Data failed: %v", err) } return qeReportCertificationData, nil } func enclaveReportToProto(b []uint8) (*pb.EnclaveReport, error) { data := clone(b) // Created an independent copy to make the interface less error-prone enclaveReport := &pb.EnclaveReport{} enclaveReport.CpuSvn = data[qeCPUSvnStart:qeCPUSvnEnd] enclaveReport.MiscSelect = binary.LittleEndian.Uint32(data[qeMiscSelectStart:qeMiscSelectEnd]) enclaveReport.Reserved1 = data[qeReserved1Start:qeReserved1End] enclaveReport.Attributes = data[qeAttributesStart:qeAttributesEnd] enclaveReport.MrEnclave = data[qeMrEnclaveStart:qeMrEnclaveEnd] enclaveReport.Reserved2 = data[qeReserved2Start:qeReserved2End] enclaveReport.MrSigner = data[qeMrSignerStart:qeMrSignerEnd] enclaveReport.Reserved3 = data[qeReserved3Start:qeReserved3End] enclaveReport.IsvProdId = uint32(binary.LittleEndian.Uint16(data[qeIsvProdIDStart:qeIsvProdIDEnd])) enclaveReport.IsvSvn = uint32(binary.LittleEndian.Uint16(data[qeIsvSvnStart:qeIsvSvnEnd])) enclaveReport.Reserved4 = data[qeReserved4Start:qeReserved4End] enclaveReport.ReportData = data[qeReportDataStart:qeReportDataEnd] if err := checkQeReport(enclaveReport); err != nil { return nil, fmt.Errorf("parsing QE Report failed: %v", err) } return enclaveReport, nil } func qeAuthDataToProto(b []uint8) (*pb.QeAuthData, uint32, error) { data := clone(b) // Created an independent copy to make the interface less error-prone authData := &pb.QeAuthData{} authData.ParsedDataSize = uint32(binary.LittleEndian.Uint16(data[authDataParsedDataSizeStart:authDataParsedDataSizeEnd])) authDataEnd := authDataParsedDataSizeEnd + authData.GetParsedDataSize() authData.Data = data[authDataStart:authDataEnd] if err := checkQeAuthData(authData); err != nil { return nil, 0, fmt.Errorf("parsing QE AuthData failed: %v", err) } return authData, authDataEnd, nil } func pckCertificateChainToProto(b []uint8) (*pb.PCKCertificateChainData, error) { data := clone(b) // Created an independent copy to make the interface less error-prone pckCertificateChain := &pb.PCKCertificateChainData{} pckCertificateChain.CertificateDataType = uint32(binary.LittleEndian.Uint16(data[pckCertChainCertificationDataTypeStart:pckCertChainCertificationDataTypeEnd])) pckCertificateChain.Size = binary.LittleEndian.Uint32(data[pckCertChainSizeStart:pckCertChainSizeEnd]) pckCertificateChain.PckCertChain = data[pckCertChainDataStart:] if err := checkPCKCertificateChain(pckCertificateChain); err != nil { return nil, fmt.Errorf("parsing PCK certification chain failed: %v", err) } return pckCertificateChain, nil } func checkHeader(header *pb.Header) error { if header == nil { return ErrHeaderNil } if header.GetVersion() >= (1 << 16) { return fmt.Errorf("version field size must fit in 2 bytes , got %d", header.GetVersion()) } if header.GetVersion() != QuoteVersion { return fmt.Errorf("version %d not supported", header.GetVersion()) } if header.GetAttestationKeyType() >= (1 << 16) { return fmt.Errorf("attestation key type field size must fit in 2 bytes , got %d", header.GetAttestationKeyType()) } if header.GetAttestationKeyType() != AttestationKeyType { return ErrAttestationKeyType } if header.GetTeeType() != TeeTDX { return ErrTeeType } if len(header.GetQeSvn()) != qeSvnSize { return fmt.Errorf("qeSvn size is %d bytes. Expected %d bytes", len(header.GetQeSvn()), qeSvnSize) } if len(header.GetPceSvn()) != pceSvnSize { return fmt.Errorf("pceSvn size is %d bytes. Expected %d bytes", len(header.GetPceSvn()), pceSvnSize) } if len(header.GetQeVendorId()) != QeVendorIDSize { return fmt.Errorf("qeVendorId size is %d bytes. Expected %d bytes", len(header.GetQeVendorId()), QeVendorIDSize) } if len(header.GetUserData()) != userDataSize { return fmt.Errorf("user data size is %d bytes. Expected %d bytes", len(header.GetUserData()), userDataSize) } return nil } func checkTDQuoteBody(tdQuoteBody *pb.TDQuoteBody) error { if tdQuoteBody == nil { return ErrTDQuoteBodyNil } if len(tdQuoteBody.GetTeeTcbSvn()) != TeeTcbSvnSize { return fmt.Errorf("teeTcbSvn size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetTeeTcbSvn()), TeeTcbSvnSize) } if len(tdQuoteBody.GetMrSeam()) != MrSeamSize { return fmt.Errorf("mrSeam size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetMrSeam()), MrSeamSize) } if len(tdQuoteBody.GetMrSignerSeam()) != mrSignerSeamSize { return fmt.Errorf("mrSignerSeam size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetMrSignerSeam()), mrSignerSeamSize) } if len(tdQuoteBody.GetSeamAttributes()) != seamAttributesSize { return fmt.Errorf("seamAttributes size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetSeamAttributes()), seamAttributesSize) } if len(tdQuoteBody.GetTdAttributes()) != TdAttributesSize { return fmt.Errorf("tdAttributes size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetTdAttributes()), TdAttributesSize) } if len(tdQuoteBody.GetXfam()) != XfamSize { return fmt.Errorf("xfam size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetXfam()), XfamSize) } if len(tdQuoteBody.GetMrTd()) != MrTdSize { return fmt.Errorf("mrTd size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetMrTd()), MrTdSize) } if len(tdQuoteBody.GetMrConfigId()) != MrConfigIDSize { return fmt.Errorf("mrConfigId size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetMrConfigId()), MrConfigIDSize) } if len(tdQuoteBody.GetMrOwner()) != MrOwnerSize { return fmt.Errorf("mrOwner size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetMrOwner()), MrOwnerSize) } if len(tdQuoteBody.GetMrOwnerConfig()) != MrOwnerConfigSize { return fmt.Errorf("mrOwnerConfig size is %d bytes. Expected %d bytes", len(tdQuoteBody.GetMrOwnerConfig()), MrOwnerConfigSize) } if len(tdQuoteBody.GetRtmrs()) != rtmrsCount { return fmt.Errorf("rtmrs count is %d. Expected %d", len(tdQuoteBody.GetRtmrs()), rtmrsCount) } for i := 0; i < rtmrsCount; i++ { if len(tdQuoteBody.GetRtmrs()[i]) != RtmrSize { return fmt.Errorf("rtmr%d size is %d bytes. Expected %d bytes", i, len(tdQuoteBody.GetRtmrs()[i]), RtmrSize) } } return nil } func checkPCKCertificateChain(chain *pb.PCKCertificateChainData) error { if chain == nil { return ErrPckCertChainNil } if chain.GetCertificateDataType() >= (1 << 16) { return fmt.Errorf("certification data type expected to be of 2 bytes, got %d", chain.GetCertificateDataType()) } if chain.GetCertificateDataType() != pckReportCertificationDataType { return fmt.Errorf("PCK certificate chain data type invalid, got %d, expected %d", chain.GetCertificateDataType(), pckReportCertificationDataType) } if chain.GetSize() != uint32(len(chain.GetPckCertChain())) { return fmt.Errorf("PCK certificate chain size is %d. Expected size %d", len(chain.GetPckCertChain()), chain.GetSize()) } return nil } func checkQeReport(report *pb.EnclaveReport) error { if report == nil { return ErrQeReportNil } if len(report.GetCpuSvn()) != cpuSvnSize { return fmt.Errorf("cpuSvn size is %d bytes. Expected %d bytes", len(report.GetCpuSvn()), cpuSvnSize) } if len(report.GetReserved1()) != reserved1Size { return fmt.Errorf("reserved1 size is %d bytes. Expected %d bytes", len(report.GetReserved1()), reserved1Size) } if len(report.GetAttributes()) != attributesSize { return fmt.Errorf("attributes size is %d bytes. Expected %d bytes", len(report.GetAttributes()), attributesSize) } if len(report.GetMrEnclave()) != mrEnclaveSize { return fmt.Errorf("mrEnclave size is %d bytes. Expected %d bytes", len(report.GetMrEnclave()), mrEnclaveSize) } if len(report.GetReserved2()) != reserved2Size { return fmt.Errorf("reserved2 size is %d bytes. Expected %d bytes", len(report.GetReserved2()), reserved2Size) } if len(report.GetMrSigner()) != mrSignerSize { return fmt.Errorf("mrSigner size is %d bytes. Expected %d bytes", len(report.GetMrSigner()), mrSignerSize) } if len(report.GetReserved3()) != reserved3Size { return fmt.Errorf("reserved3 size is %d bytes. Expected %d bytes", len(report.GetReserved3()), reserved3Size) } if report.GetIsvProdId() >= (1 << 16) { return fmt.Errorf("isVProdId field size must fit in 2 bytes , got %d", report.GetIsvProdId()) } if report.GetIsvSvn() >= (1 << 16) { return fmt.Errorf("isVSvn field size must fit in 2 bytes , got %d", report.GetIsvSvn()) } if len(report.GetReserved4()) != reserved4Size { return fmt.Errorf("reserved4 size is %d bytes. Expected %d bytes", len(report.GetReserved4()), reserved4Size) } if len(report.GetReportData()) != ReportDataSize { return fmt.Errorf("report data size is %d bytes. Expected %d bytes", len(report.GetReportData()), ReportDataSize) } return nil } func checkQeAuthData(authData *pb.QeAuthData) error { if authData == nil { return ErrQeAuthDataNil } if authData.GetParsedDataSize() >= (1 << 16) { return fmt.Errorf("parsed data size field size must fit in 2 bytes , got %d", authData.GetParsedDataSize()) } if authData.GetParsedDataSize() != uint32(len(authData.GetData())) { return fmt.Errorf("parsed data size is %d bytes. Expected %d bytes", len(authData.GetData()), authData.GetParsedDataSize()) } return nil } func checkQeReportCertificationData(qeReport *pb.QEReportCertificationData) error { if qeReport == nil { return ErrQeReportCertificationDataNil } if err := checkQeReport(qeReport.GetQeReport()); err != nil { return fmt.Errorf("QE Report error: %v", err) } if len(qeReport.GetQeReportSignature()) != signatureSize { return fmt.Errorf("signature size is %d bytes. Expected %d bytes", len(qeReport.GetQeReportSignature()), signatureSize) } if err := checkQeAuthData(qeReport.GetQeAuthData()); err != nil { return fmt.Errorf("QE AuthData error: %v", err) } if err := checkPCKCertificateChain(qeReport.GetPckCertificateChainData()); err != nil { return fmt.Errorf("PCK certificate chain error: %v", err) } return nil } func checkCertificationData(certification *pb.CertificationData) error { if certification == nil { return ErrCertificationDataNil } if certification.GetCertificateDataType() >= (1 << 16) { return fmt.Errorf("certification data type field size must fit in 2 bytes , got %d", certification.GetCertificateDataType()) } if certification.GetCertificateDataType() != qeReportCertificationDataType { return fmt.Errorf("certification data type invalid, got %d, expected %d", certification.GetCertificateDataType(), qeReportCertificationDataType) } if err := checkQeReportCertificationData(certification.GetQeReportCertificationData()); err != nil { return fmt.Errorf("QE Report certification data error: %v", err) } return nil } func checkEcdsa256BitQuoteV4AuthData(signedData *pb.Ecdsa256BitQuoteV4AuthData) error { if signedData == nil { return ErrQuoteV4AuthDataNil } if len(signedData.GetSignature()) != signatureSize { return fmt.Errorf("signature size is %d bytes. Expected %d bytes", len(signedData.GetSignature()), signatureSize) } if len(signedData.GetEcdsaAttestationKey()) != attestationKeySize { return fmt.Errorf("ecdsa attestation key size is %d bytes. Expected %d bytes", len(signedData.GetEcdsaAttestationKey()), attestationKeySize) } if err := checkCertificationData(signedData.GetCertificationData()); err != nil { return fmt.Errorf("certification data error: %v", err) } return nil } // CheckQuoteV4 validates a quote protobuf by ensuring all parameters meet their required size func CheckQuoteV4(quote *pb.QuoteV4) error { if quote == nil { return ErrQuoteV4Nil } if err := checkHeader(quote.GetHeader()); err != nil { return fmt.Errorf("QuoteV4 Header error: %v", err) } if err := checkTDQuoteBody(quote.GetTdQuoteBody()); err != nil { return fmt.Errorf("QuoteV4 TD Quote Body error: %v", err) } if err := checkEcdsa256BitQuoteV4AuthData(quote.GetSignedData()); err != nil { return fmt.Errorf("QuoteV4 AuthData error: %v", err) } return nil } // EnclaveReportToAbiBytes translates the EnclaveReport back into its little-endian ABI format func EnclaveReportToAbiBytes(report *pb.EnclaveReport) ([]byte, error) { if report == nil { return nil, ErrQeReportNil } if err := checkQeReport(report); err != nil { return nil, fmt.Errorf("QE Report invalid: %v", err) } data := make([]byte, qeReportSize) copy(data[qeCPUSvnStart:qeCPUSvnEnd], report.GetCpuSvn()) binary.LittleEndian.PutUint32(data[qeMiscSelectStart:qeMiscSelectEnd], report.GetMiscSelect()) copy(data[qeReserved1Start:qeReserved1End], report.GetReserved1()) copy(data[qeAttributesStart:qeAttributesEnd], report.GetAttributes()) copy(data[qeMrEnclaveStart:qeMrEnclaveEnd], report.GetMrEnclave()) copy(data[qeReserved2Start:qeReserved2End], report.GetReserved2()) copy(data[qeMrSignerStart:qeMrSignerEnd], report.GetMrSigner()) copy(data[qeReserved3Start:qeReserved3End], report.GetReserved3()) binary.LittleEndian.PutUint16(data[qeIsvProdIDStart:qeIsvProdIDEnd], uint16(report.GetIsvProdId())) binary.LittleEndian.PutUint16(data[qeIsvSvnStart:qeIsvSvnEnd], uint16(report.GetIsvSvn())) copy(data[qeReserved4Start:qeReserved4End], report.GetReserved4()) copy(data[qeReportDataStart:qeReportDataEnd], report.GetReportData()) return data, nil } // HeaderToAbiBytes translates the Header back into its little-endian ABI format func HeaderToAbiBytes(header *pb.Header) ([]byte, error) { if header == nil { return nil, ErrHeaderNil } if err := checkHeader(header); err != nil { return nil, fmt.Errorf("header invalid: %v", err) } data := make([]byte, headerSize) binary.LittleEndian.PutUint16(data[headerVersionStart:headerVersionEnd], uint16(header.GetVersion())) binary.LittleEndian.PutUint16(data[headerAttestationKeyTypeStart:headerAttestationKeyTypeEnd], uint16(header.GetAttestationKeyType())) binary.LittleEndian.PutUint32(data[headerTeeTypeStart:headerTeeTypeEnd], (header.GetTeeType())) copy(data[headerPceSvnStart:headerPceSvnEnd], header.GetPceSvn()) copy(data[headerQeSvnStart:headerQeSvnEnd], header.GetQeSvn()) copy(data[headerQeVendorIDStart:headerQeVendorIDEnd], header.GetQeVendorId()) copy(data[headerUserDataStart:headerUserDataEnd], header.GetUserData()) return data, nil } // TdQuoteBodyToAbiBytes translates the TDQuoteBody back into its little-endian ABI format func TdQuoteBodyToAbiBytes(tdQuoteBody *pb.TDQuoteBody) ([]byte, error) { if tdQuoteBody == nil { return nil, ErrTDQuoteBodyNil } if err := checkTDQuoteBody(tdQuoteBody); err != nil { return nil, fmt.Errorf("TD quote body invalid: %v", err) } data := make([]byte, tdQuoteBodySize) copy(data[tdTeeTcbSvnStart:tdTeeTcbSvnEnd], tdQuoteBody.GetTeeTcbSvn()) copy(data[tdMrSeamStart:tdMrSeamEnd], tdQuoteBody.GetMrSeam()) copy(data[tdMrSignerSeamStart:tdMrSignerSeamEnd], tdQuoteBody.GetMrSignerSeam()) copy(data[tdSeamAttributesStart:tdSeamAttributesEnd], tdQuoteBody.GetSeamAttributes()) copy(data[tdAttributesStart:tdAttributesEnd], tdQuoteBody.GetTdAttributes()) copy(data[tdXfamStart:tdXfamEnd], tdQuoteBody.GetXfam()) copy(data[tdMrTdStart:tdMrTdEnd], tdQuoteBody.GetMrTd()) copy(data[tdMrConfigIDStart:tdMrConfigIDEnd], tdQuoteBody.GetMrConfigId()) copy(data[tdMrOwnerStart:tdMrOwnerEnd], tdQuoteBody.GetMrOwner()) copy(data[tdMrOwnerConfigStart:tdMrOwnerConfigEnd], tdQuoteBody.GetMrOwnerConfig()) rtmrsStart := tdRtmrsStart for i := 0; i < rtmrsCount; i++ { rtmrsEnd := rtmrsStart + RtmrSize copy(data[rtmrsStart:rtmrsEnd], tdQuoteBody.GetRtmrs()[i]) rtmrsStart += RtmrSize } copy(data[tdReportDataStart:tdReportDataEnd], tdQuoteBody.GetReportData()) return data, nil } func pckCertificateChainToAbiBytes(pckCertificateChain *pb.PCKCertificateChainData) ([]byte, error) { if pckCertificateChain == nil { return nil, ErrPckCertChainNil } if err := checkPCKCertificateChain(pckCertificateChain); err != nil { return nil, fmt.Errorf("PCK certificate chain data invalid: %v", err) } data := make([]byte, pckCertificateChainKnownSize+pckCertificateChain.GetSize()) binary.LittleEndian.PutUint16(data[pckCertChainCertificationDataTypeStart:pckCertChainCertificationDataTypeEnd], uint16(pckCertificateChain.GetCertificateDataType())) binary.LittleEndian.PutUint32(data[pckCertChainSizeStart:pckCertChainSizeEnd], pckCertificateChain.GetSize()) copy(data[pckCertChainDataStart:], pckCertificateChain.GetPckCertChain()) return data, nil } func qeAuthDataToAbiBytes(authData *pb.QeAuthData) ([]byte, error) { if authData == nil { return nil, ErrQeAuthDataNil } if err := checkQeAuthData(authData); err != nil { return nil, fmt.Errorf("QE AuthData invalid: %v", err) } data := make([]byte, qeAuthDataKnownSize+authData.GetParsedDataSize()) binary.LittleEndian.PutUint16(data[authDataParsedDataSizeStart:authDataParsedDataSizeEnd], uint16(authData.GetParsedDataSize())) copy(data[authDataStart:], authData.GetData()) return data, nil } func qeReportCertificationDataToAbiBytes(qeReport *pb.QEReportCertificationData) ([]byte, error) { if qeReport == nil { return nil, ErrQeReportCertificationDataNil } if err := checkQeReportCertificationData(qeReport); err != nil { return nil, fmt.Errorf("QE Report certification data invalid: %v", err) } data, err := EnclaveReportToAbiBytes(qeReport.GetQeReport()) if err != nil { return nil, fmt.Errorf("enclave report to abi bytes conversion failed: %v", err) } qeReportSignatureData := clone(qeReport.GetQeReportSignature()) data = append(data, qeReportSignatureData...) qeAuthData, err := qeAuthDataToAbiBytes(qeReport.GetQeAuthData()) if err != nil { return nil, fmt.Errorf("QE AuthData to abi bytes conversion failed: %v", err) } data = append(data, qeAuthData...) pckCertificateChainData, err := pckCertificateChainToAbiBytes(qeReport.GetPckCertificateChainData()) if err != nil { return nil, fmt.Errorf("PCK certificate chain to abi bytes conversion failed: %v", err) } data = append(data, pckCertificateChainData...) return data, nil } func certificationDataToAbiBytes(certification *pb.CertificationData) ([]byte, error) { if certification == nil { return nil, ErrCertificationDataNil } if err := checkCertificationData(certification); err != nil { return nil, fmt.Errorf("certification data invalid: %v", err) } data := make([]byte, certificationDataKnownSize) binary.LittleEndian.PutUint16(data[certificateDataTypeStart:certificateDataTypeEnd], uint16(certification.GetCertificateDataType())) binary.LittleEndian.PutUint32(data[certificateSizeStart:certificateSizeEnd], certification.GetSize()) certificationData, err := qeReportCertificationDataToAbiBytes(certification.GetQeReportCertificationData()) if err != nil { return nil, fmt.Errorf("QE Report certification data to abi bytes conversion failed: %v", err) } data = append(data, certificationData...) return data, nil } func signedDataToAbiBytes(signedData *pb.Ecdsa256BitQuoteV4AuthData) ([]byte, error) { if signedData == nil { return nil, ErrQuoteV4AuthDataNil } if err := checkEcdsa256BitQuoteV4AuthData(signedData); err != nil { return nil, fmt.Errorf("QuoteV4 AuthData invalid: %v", err) } data := make([]byte, quoteV4AuthDataKnownSize) copy(data[signedDataSignatureStart:signedDataSignatureEnd], signedData.GetSignature()) copy(data[signedDataAttestationKeyStart:signedDataAttestationKeyEnd], signedData.GetEcdsaAttestationKey()) certificationData, err := certificationDataToAbiBytes(signedData.GetCertificationData()) if err != nil { return nil, fmt.Errorf("signed data certification data to abi bytes conversion failed: %v", err) } data = append(data, certificationData...) return data, nil } // QuoteToAbiBytes translates the Quote back into its little-endian ABI format. // Supported quote formats - QuoteV4. func QuoteToAbiBytes(quote any) ([]byte, error) { if quote == nil { return nil, ErrQuoteNil } switch q := quote.(type) { case *pb.QuoteV4: return quoteToAbiBytesV4(q) default: return nil, fmt.Errorf("unsupported quote type: %T", quote) } } // quoteToAbiBytesV4 translates the QuoteV4 back into its little-endian ABI format func quoteToAbiBytesV4(quote *pb.QuoteV4) ([]byte, error) { if err := CheckQuoteV4(quote); err != nil { return nil, fmt.Errorf("QuoteV4 invalid: %v", err) } var data []byte headerData, err := HeaderToAbiBytes(quote.GetHeader()) if err != nil { return nil, fmt.Errorf("header to abi bytes conversion failed: %v", err) } data = append(data, headerData...) tdReportData, err := TdQuoteBodyToAbiBytes(quote.GetTdQuoteBody()) if err != nil { return nil, fmt.Errorf("TD quote body to abi bytes conversion failed: %v", err) } data = append(data, tdReportData...) signedDataSizeBytes := make([]byte, 0x04) binary.LittleEndian.PutUint32(signedDataSizeBytes[0x00:0x04], quote.GetSignedDataSize()) data = append(data, signedDataSizeBytes...) signedData, err := signedDataToAbiBytes(quote.GetSignedData()) if err != nil { return nil, fmt.Errorf("signed data to abi bytes conversion failed: %v", err) } data = append(data, signedData...) if quote.GetExtraBytes() != nil { data = append(data, quote.GetExtraBytes()...) } return data, nil } // SignatureToDER converts the signature to DER format func SignatureToDER(x []byte) ([]byte, error) { if len(x) != signatureSize { return nil, fmt.Errorf("signature size is %d bytes. Expected %d bytes", len(x), signatureSize) } var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(new(big.Int).SetBytes(ecdsaGetR(x))) b.AddASN1BigInt(new(big.Int).SetBytes(ecdsaGetS(x))) }) return b.Bytes() } func ecdsaGetR(signature []byte) []byte { return signature[0x0:0x20] } func ecdsaGetS(signature []byte) []byte { return signature[0x20:0x40] } go-tdx-guest-0.3.1/abi/abi_test.go000066400000000000000000000152631456371376300167440ustar00rootroot00000000000000// 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 abi import ( "bytes" "testing" pb "github.com/google/go-tdx-guest/proto/tdx" test "github.com/google/go-tdx-guest/testing/testdata" ) func TestQuoteToProto(t *testing.T) { expectedError := "unable to determine quote format since bytes length is less than 2 bytes" var emptyRawQuote []uint8 _, err := QuoteToProto(emptyRawQuote) if err == nil || err.Error() != expectedError { t.Errorf("error found: %v, want error: %s", err, expectedError) } _, err = QuoteToProto(test.RawQuote) if err != nil { t.Fatal(err) } } func TestQuoteToAbiBytes(t *testing.T) { quote, err := QuoteToProto(test.RawQuote) if err != nil { t.Fatal(err) } rawQuote, err := QuoteToAbiBytes(quote) if err != nil { t.Fatal(err) } if !bytes.Equal(test.RawQuote, rawQuote) { t.Errorf("raw quote bytes got %v. Expected %v", rawQuote, test.RawQuote) } } func TestNilToAbiBytesConversions(t *testing.T) { if _, err := QuoteToAbiBytes(nil); err != ErrQuoteNil { t.Error(err) } if _, err := signedDataToAbiBytes(nil); err != ErrQuoteV4AuthDataNil { t.Error(err) } if _, err := certificationDataToAbiBytes(nil); err != ErrCertificationDataNil { t.Error(err) } if _, err := qeReportCertificationDataToAbiBytes(nil); err != ErrQeReportCertificationDataNil { t.Error(err) } if _, err := qeAuthDataToAbiBytes(nil); err != ErrQeAuthDataNil { t.Error(err) } if _, err := pckCertificateChainToAbiBytes(nil); err != ErrPckCertChainNil { t.Error(err) } if _, err := TdQuoteBodyToAbiBytes(nil); err != ErrTDQuoteBodyNil { t.Error(err) } if _, err := HeaderToAbiBytes(nil); err != ErrHeaderNil { t.Error(err) } if _, err := EnclaveReportToAbiBytes(nil); err != ErrQeReportNil { t.Error(err) } } func TestInvalidConversionsToAbiBytes(t *testing.T) { expectedErrors := []string{ "QuoteV4 invalid: QuoteV4 Header error: header is nil", "QuoteV4 AuthData invalid: signature size is 0 bytes. Expected 64 bytes", "certification data invalid: certification data type invalid, got 0, expected 6", "certification data invalid: certification data type invalid, got 7, expected 6", "certification data invalid: QE Report certification data error: QE Report certification data is nil", "QE Report certification data invalid: QE Report error: QE Report is nil", "QE AuthData invalid: parsed data size is 0 bytes. Expected 1 bytes", "PCK certificate chain data invalid: PCK certificate chain data type invalid, got 0, expected 5", "PCK certificate chain data invalid: PCK certificate chain data type invalid, got 7, expected 5", "PCK certificate chain data invalid: PCK certificate chain size is 0. Expected size 2", "TD quote body invalid: teeTcbSvn size is 0 bytes. Expected 16 bytes", "header invalid: version 0 not supported", "header invalid: version 1 not supported", "header invalid: attestation key type not supported", "header invalid: TEE type is not TDX", "QE Report invalid: cpuSvn size is 0 bytes. Expected 16 bytes", } if _, err := QuoteToAbiBytes(&pb.QuoteV4{}); err == nil || err.Error() != expectedErrors[0] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[0]) } if _, err := signedDataToAbiBytes(&pb.Ecdsa256BitQuoteV4AuthData{}); err == nil || err.Error() != expectedErrors[1] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[1]) } if _, err := certificationDataToAbiBytes(&pb.CertificationData{}); err == nil || err.Error() != expectedErrors[2] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[2]) } if _, err := certificationDataToAbiBytes(&pb.CertificationData{CertificateDataType: 7}); err == nil || err.Error() != expectedErrors[3] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[3]) } if _, err := certificationDataToAbiBytes(&pb.CertificationData{CertificateDataType: qeReportCertificationDataType, Size: 2}); err == nil || err.Error() != expectedErrors[4] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[4]) } if _, err := qeReportCertificationDataToAbiBytes(&pb.QEReportCertificationData{}); err == nil || err.Error() != expectedErrors[5] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[5]) } if _, err := qeAuthDataToAbiBytes(&pb.QeAuthData{ParsedDataSize: 1}); err == nil || err.Error() != expectedErrors[6] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[6]) } if _, err := pckCertificateChainToAbiBytes(&pb.PCKCertificateChainData{}); err == nil || err.Error() != expectedErrors[7] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[7]) } if _, err := pckCertificateChainToAbiBytes(&pb.PCKCertificateChainData{CertificateDataType: 7}); err == nil || err.Error() != expectedErrors[8] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[8]) } if _, err := pckCertificateChainToAbiBytes(&pb.PCKCertificateChainData{CertificateDataType: pckReportCertificationDataType, Size: 2}); err == nil || err.Error() != expectedErrors[9] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[9]) } if _, err := TdQuoteBodyToAbiBytes(&pb.TDQuoteBody{}); err == nil || err.Error() != expectedErrors[10] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[10]) } if _, err := HeaderToAbiBytes(&pb.Header{}); err == nil || err.Error() != expectedErrors[11] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[11]) } if _, err := HeaderToAbiBytes(&pb.Header{Version: 1}); err == nil || err.Error() != expectedErrors[12] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[12]) } if _, err := HeaderToAbiBytes(&pb.Header{Version: QuoteVersion, AttestationKeyType: 1}); err == nil || err.Error() != expectedErrors[13] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[13]) } if _, err := HeaderToAbiBytes(&pb.Header{Version: QuoteVersion, AttestationKeyType: AttestationKeyType, TeeType: 0x01}); err == nil || err.Error() != expectedErrors[14] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[14]) } if _, err := EnclaveReportToAbiBytes(&pb.EnclaveReport{}); err == nil || err.Error() != expectedErrors[15] { t.Errorf("error found: %v, want error: %s", err, expectedErrors[15]) } } go-tdx-guest-0.3.1/client/000077500000000000000000000000001456371376300153375ustar00rootroot00000000000000go-tdx-guest-0.3.1/client/client.go000066400000000000000000000120271456371376300171460ustar00rootroot00000000000000// 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 client provides the library functions to get a TDX quote // from the TDX guest device package client import ( "flag" "fmt" "os" "github.com/google/go-tdx-guest/abi" labi "github.com/google/go-tdx-guest/client/linuxabi" "github.com/google/logger" ) var tdxGuestPath = flag.String("tdx_guest_device_path", "default", "Path to TDX guest device. If \"default\", uses platform default or a fake if testing.") // Device encapsulates the possible commands to the TDX guest device. // Deprecated: The Device interface is deprecated, and use of quote provider interface is // recommended for fetching attestation quote. type Device interface { Open(path string) error Close() error Ioctl(command uintptr, argument any) (uintptr, error) } // QuoteProvider encapsulates calls to attestation quote. type QuoteProvider interface { IsSupported() error GetRawQuote(reportData [64]byte) ([]uint8, error) } // UseDefaultTdxGuestDevice returns true if tdxGuestPath=default. func UseDefaultTdxGuestDevice() bool { return *tdxGuestPath == "default" } // getReport requests for tdx report by making an ioctl call. func getReport(d Device, reportData [64]byte) ([]uint8, error) { tdxReportReq := labi.TdxReportReq{} copy(tdxReportReq.ReportData[:], reportData[:]) result, err := d.Ioctl(labi.IocTdxGetReport, &tdxReportReq) if err != nil { return nil, err } if result != uintptr(labi.TdxAttestSuccess) { return nil, fmt.Errorf("unable to get the report: %d", result) } return tdxReportReq.TdReport[:], nil } // GetRawQuote uses Quote provider or Device(deprecated) to get the quote in byte array. func GetRawQuote(quoteProvider any, reportData [64]byte) ([]uint8, error) { switch qp := quoteProvider.(type) { case Device: return getRawQuoteViaDevice(qp, reportData) case QuoteProvider: return getRawQuoteViaProvider(qp, reportData) default: return nil, fmt.Errorf("unsupported quote provider type: %T", quoteProvider) } } // getRawQuoteViaDevice uses TDX device driver to call getReport for report and convert it to // quote using an ioctl call. func getRawQuoteViaDevice(d Device, reportData [64]byte) ([]uint8, error) { logger.V(1).Info("Get raw TDX quote via Device") tdReport, err := getReport(d, reportData) if err != nil { return nil, err } tdxHdr := &labi.TdxQuoteHdr{ Status: 0, Version: 1, InLen: labi.TdReportSize, OutLen: 0, } copy(tdxHdr.Data[:], tdReport[:labi.TdReportSize]) tdxReq := labi.TdxQuoteReq{ Buffer: tdxHdr, Length: labi.ReqBufSize, } result, err := d.Ioctl(labi.IocTdxGetQuote, &tdxReq) if err != nil { return nil, err } if result != uintptr(labi.TdxAttestSuccess) { return nil, fmt.Errorf("unable to get the quote") } if tdxHdr.Status != 0 { if labi.GetQuoteInFlight == tdxHdr.Status { return nil, fmt.Errorf("the device driver return busy") } else if labi.GetQuoteServiceUnavailable == tdxHdr.Status { return nil, fmt.Errorf("request feature is not supported") } else if tdxHdr.OutLen == 0 || tdxHdr.OutLen > labi.ReqBufSize { return nil, fmt.Errorf("invalid Quote size: %v. It must be > 0 and <= : %v", tdxHdr.OutLen, labi.ReqBufSize) } return nil, fmt.Errorf("unexpected error: %v", tdxHdr.Status) } return tdxHdr.Data[:tdxHdr.OutLen], nil } // getRawQuoteViaProvider use QuoteProvider to fetch quote in byte array format. func getRawQuoteViaProvider(qp QuoteProvider, reportData [64]byte) ([]uint8, error) { if err := qp.IsSupported(); err == nil { logger.V(1).Info("Get raw TDX quote via QuoteProvider") quote, err := qp.GetRawQuote(reportData) return quote, err } return fallbackToDeviceForRawQuote(reportData) } // GetQuote uses Quote provider or Device(deprecated) to get the quote in byte array and convert it // into proto. // Supported quote formats - QuoteV4. func GetQuote(quoteProvider any, reportData [64]byte) (any, error) { quotebytes, err := GetRawQuote(quoteProvider, reportData) if err != nil { return nil, err } quote, err := abi.QuoteToProto(quotebytes) if err != nil { return nil, err } return quote, nil } // fallbackToDeviceForRawQuote opens tdx_guest device to fetch raw quote. func fallbackToDeviceForRawQuote(reportData [64]byte) ([]uint8, error) { // Fall back to TDX device driver. device, err := OpenDevice() if err != nil { return nil, fmt.Errorf("neither TDX device, nor ConfigFs is available to fetch attestation quote") } bytes, err := getRawQuoteViaDevice(device, reportData) device.Close() return bytes, err } func init() { logger.Init("", false, false, os.Stdout) } go-tdx-guest-0.3.1/client/client_linux.go000066400000000000000000000073231456371376300203700ustar00rootroot00000000000000// 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. //go:build linux // Package client provides an interface to the Intel TDX guest device commands. package client import ( "fmt" "unsafe" "github.com/google/go-configfs-tsm/configfs/linuxtsm" "github.com/google/go-configfs-tsm/report" labi "github.com/google/go-tdx-guest/client/linuxabi" "golang.org/x/sys/unix" ) // defaultTdxGuestDevicePath is the platform's usual device path to the TDX guest. const defaultTdxGuestDevicePath = "/dev/tdx_guest" // LinuxDevice implements the Device interface with Linux ioctls. type LinuxDevice struct { fd int } // Open opens the TDX guest device from a given path func (d *LinuxDevice) Open(path string) error { fd, err := unix.Open(path, unix.O_RDWR|unix.O_SYNC, 0) if err != nil { d.fd = -1 return fmt.Errorf("could not open Intel TDX guest device at %q: %v", path, err) } d.fd = fd return nil } // OpenDevice opens the TDX guest device. func OpenDevice() (*LinuxDevice, error) { result := &LinuxDevice{} path := *tdxGuestPath if UseDefaultTdxGuestDevice() { path = defaultTdxGuestDevicePath } if err := result.Open(path); err != nil { return nil, err } return result, nil } // Close closes the TDX 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) { if d.fd == -1 { return 0, fmt.Errorf("intel TDX Guest Device is not open") } switch sreq := req.(type) { case *labi.TdxQuoteReq: abi := sreq.ABI() result, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(d.fd), command, uintptr(abi.Pointer())) abi.Finish(sreq) if errno == unix.EBUSY { return 0, errno } else if errno == unix.ENOTTY { return 0, fmt.Errorf("invalid ioctl! use QuoteProvider to fetch attestation quote") } else if errno != 0 { return 0, errno } return result, nil case *labi.TdxReportReq: result, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(d.fd), command, uintptr(unsafe.Pointer(req.(*labi.TdxReportReq)))) if errno != 0 { return 0, errno } return result, nil } return 0, fmt.Errorf("unexpected request value: %v", req) } // 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() error { _, err := linuxtsm.MakeClient() return err } // GetRawQuote returns byte format attestation quote via ConfigFS. func (p *LinuxConfigFsQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { req := &report.Request{ InBlob: reportData[:], GetAuxBlob: false, } resp, err := linuxtsm.GetReport(req) if err != nil { return nil, err } tdReport := resp.OutBlob return tdReport, nil } // GetQuoteProvider returns an instance of LinuxConfigFsQuoteProvider. func GetQuoteProvider() (*LinuxConfigFsQuoteProvider, error) { return &LinuxConfigFsQuoteProvider{}, nil } go-tdx-guest-0.3.1/client/client_macos.go000066400000000000000000000040551456371376300203320ustar00rootroot00000000000000// 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. //go:build darwin // Package client provides an interface to the Intel TDX guest device commands. package client import ( "fmt" ) // defaultTdxGuestDevicePath is the platform's usual device path to the TDX guest. const defaultTdxGuestDevicePath = "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") } // MacOsConfigFsQuoteProvider implements the QuoteProvider interface to fetch attestation quote via ConfigFS. type MacOsConfigFsQuoteProvider struct{} // IsSupported is not supported on MacOS. func (p *MacOsConfigFsQuoteProvider) IsSupported() error { return fmt.Errorf("MacOS is unsupported") } // GetRawQuote is not supported on MacOS. func (p *MacOsConfigFsQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { return nil, fmt.Errorf("MacOS is unsupported") } // GetQuoteProvider is not supported on MacOS. func GetQuoteProvider() (*MacOsConfigFsQuoteProvider, error) { return nil, fmt.Errorf("MacOS is unsupported") } go-tdx-guest-0.3.1/client/client_test.go000066400000000000000000000103571456371376300202110ustar00rootroot00000000000000// 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 client import ( "bytes" "fmt" "sync" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-tdx-guest/abi" test "github.com/google/go-tdx-guest/testing" "google.golang.org/protobuf/testing/protocmp" ) var devMu sync.Once var device Device var quoteProvider QuoteProvider var tests []test.TestCase const defaultTDXDevicePath = "/dev/tdx-guest" func initialize() { for _, tc := range test.TestCases() { // Don't test faked errors when running real hardware tests. if !UseDefaultTdxGuestDevice() && tc.WantErr != "" { continue } tests = append(tests, tc) } tdxTestQuoteProvider, err := test.TcQuoteProvider(tests) if err != nil { panic(fmt.Sprintf("failed to create test quote provider: %v", err)) } quoteProvider = tdxTestQuoteProvider // Choose a mock device or a real device depending on the --tdx_guest_device_path flag. if UseDefaultTdxGuestDevice() { tdxTestDevice, err := test.TcDevice(tests) if err != nil { panic(fmt.Sprintf("failed to create test device: %v", err)) } if err := tdxTestDevice.Open(defaultTDXDevicePath); err != nil { panic(err) } device = tdxTestDevice return } client, err := OpenDevice() if err != nil { panic(err) } device = client } func TestGetReport(t *testing.T) { devMu.Do(initialize) for _, tc := range test.TestCases() { t.Run(tc.Name, func(t *testing.T) { got, err := getReport(device, tc.Input) if err != nil { t.Errorf("failed to get the report: %v", err) } if tc.WantErr == "" { want := tc.Report if !bytes.Equal(got[:], want[:]) { t.Errorf("Got %v want %v", got, want) } } }) } } func TestGetRawQuote(t *testing.T) { devMu.Do(initialize) for _, tc := range test.TestCases() { t.Run(tc.Name, func(t *testing.T) { got, err := GetRawQuote(device, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("GetRawQuote(device, %v) = %v, %v. Want err: %q", tc.Input, got, err, tc.WantErr) } if tc.WantErr == "" { want := tc.Quote if !bytes.Equal(got, want) { t.Errorf("GetRawQuote(device, %v) = %v want %v", tc.Input, got, want) } } }) } } func TestGetQuote(t *testing.T) { devMu.Do(initialize) for _, tc := range test.TestCases() { t.Run(tc.Name, func(t *testing.T) { got, err := GetQuote(device, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("Expected %v got err: %v", err, tc.WantErr) } if tc.WantErr == "" { quote, err := abi.QuoteToProto(tc.Quote) if err != nil { t.Error(err) } if diff := cmp.Diff(got, quote, protocmp.Transform()); diff != "" { t.Errorf("Difference in quote: %s", diff) } } }) } } func TestGetRawQuoteViaProvider(t *testing.T) { devMu.Do(initialize) for _, tc := range test.TestCases() { t.Run(tc.Name, func(t *testing.T) { got, err := GetRawQuote(quoteProvider, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("GetRawQuoteViaProvider(quoteProvider, %v) = %v, %v. Want err: %q", tc.Input, got, err, tc.WantErr) } if tc.WantErr == "" { want := tc.Quote if !bytes.Equal(got, want) { t.Errorf("GetRawQuoteViaProvider(quoteProvider, %v) = %v want %v", tc.Input, got, want) } } }) } } func TestGetQuoteViaProvider(t *testing.T) { devMu.Do(initialize) for _, tc := range test.TestCases() { t.Run(tc.Name, func(t *testing.T) { got, err := GetQuote(quoteProvider, tc.Input) if !test.Match(err, tc.WantErr) { t.Fatalf("Expected %v got err: %v", err, tc.WantErr) } if tc.WantErr == "" { quote, err := abi.QuoteToProto(tc.Quote) if err != nil { t.Error(err) } if diff := cmp.Diff(got, quote, protocmp.Transform()); diff != "" { t.Errorf("Difference in quote: %s", diff) } } }) } } go-tdx-guest-0.3.1/client/client_windows.go000066400000000000000000000037161456371376300207250ustar00rootroot00000000000000// 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. //go:build windows package client import ( "fmt" ) // 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") } // WindowsConfigFsQuoteProvider implements the QuoteProvider interface to fetch attestation quote via ConfigFS. type WindowsConfigFsQuoteProvider struct{} // IsSupported is not supported on Windows. func (p *WindowsConfigFsQuoteProvider) IsSupported() error { return fmt.Errorf("Windows is unsupported") } // GetRawQuote is not supported on Windows. func (p *WindowsConfigFsQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { return nil, fmt.Errorf("Windows is unsupported") } // GetQuoteProvider is not supported on Windows. func GetQuoteProvider() (*WindowsConfigFsQuoteProvider, error) { return nil, fmt.Errorf("Windows is unsupported") } go-tdx-guest-0.3.1/client/linuxabi/000077500000000000000000000000001456371376300171525ustar00rootroot00000000000000go-tdx-guest-0.3.1/client/linuxabi/linux_abi.go000066400000000000000000000134371456371376300214630ustar00rootroot00000000000000// 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 linuxabi describes the ABI required for the TDX ioctl commands package linuxabi import ( "fmt" "reflect" "unsafe" ) 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/tdx-guest ioctl interface iocTypeTdxGuestReq = 'T' iocTdxWithoutNrWithoutSize = ((iocWrite | iocRead) << iocDirshift) | (iocTypeTdxGuestReq << iocTypeshift) // IocTdxGetReport is the ioctl command for getting an attestation report. IocTdxGetReport = iocTdxWithoutNrWithoutSize | (unsafe.Sizeof(TdxReportReq{}) << iocSizeshift) | (0x1 << iocNrshift) // IocTdxGetQuote is the ioctl command for getting an attestation quote. IocTdxGetQuote = iocTdxWithoutNrWithoutSize | (unsafe.Sizeof(TdxQuoteReqABI{}) << iocSizeshift) | (0x2 << iocNrshift) // TdReportDataSize is a constant for TDX ReportData size TdReportDataSize = 64 // TdReportSize is a constant for TDX Report size TdReportSize = 1024 // HeaderSize is the size of header to serialized quote request HeaderSize = 4 // ReqBufSize is a constant for serialized Tdx quote response ReqBufSize = 4 * 4 * 1024 // TdxUUIDSize is a constant for intel TDQE ID TdxUUIDSize = 16 // GetQuoteReq is a constant for report request GetQuoteReq = 0 // GetQuoteResp is a constant for report response GetQuoteResp = 1 ) // EsResult is the status code type for Linux's GHCB communication results. type EsResult int // constant for TD quote status code. const ( GetQuoteSuccess = 0 GetQuoteInFlight = 0xffffffffffffffff GetQuoteError = 0x8000000000000000 GetQuoteServiceUnavailable = 0x8000000000000001 ) const ( // TdxAttestSuccess denotes success TdxAttestSuccess = iota // TdxAttestErrorBusy returns when device driver is busy TdxAttestErrorBusy = 0x0009 // TdxAttestErrorQuoteFailure denotes failure to get the TD Quote TdxAttestErrorQuoteFailure = 0x0008 // TdxAttestErrorNotSupported denotes request feature is not supported TdxAttestErrorNotSupported = 0x0007 // TdxAttestErrorUnexpected denotes Unexpected error TdxAttestErrorUnexpected = 0x0001 ) // TdxReportReq is Linux's tdx-guest ABI for TDX Report. The // types here enhance runtime safety when using Ioctl as an interface. type TdxReportReq struct { /* Report data of 64 bytes */ ReportData [TdReportDataSize]byte /* Actual TD Report Data */ TdReport [TdReportSize]byte } // MsgHeader is used to add header field to serialized request and response message. type MsgHeader struct { MajorVersion uint16 MinorVersion uint16 MsgType uint32 Size uint32 // size of the whole message, include this header, in byte ErrorCode uint32 // used in response only } // SerializedGetQuoteReq is used to serialized the request message to get quote. type SerializedGetQuoteReq struct { Header MsgHeader // header.type = GET_QUOTE_REQ ReportSize uint32 // cannot be 0 IDListSize uint32 // length of id_list, in byte, can be 0 ReportIDList [TdReportSize]uint8 // report followed by id list - [TODO revisit if attestation key ID is included] } // TdxQuoteHdr is Linux's tdx-guest ABI for quote header type TdxQuoteHdr struct { /* Quote version, filled by TD */ Version uint64 /* Status code of Quote request, filled by VMM */ Status uint64 /* Length of TDREPORT, filled by TD */ InLen uint32 /* Length of Quote, filled by VMM */ OutLen uint32 /* Actual Quote data or TDREPORT on input */ Data [ReqBufSize]byte } // ABI returns the object itself. func (r *TdxQuoteHdr) ABI() BinaryConversion { return r } // Pointer returns a pointer to the object itself. func (r *TdxQuoteHdr) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish is a no-op. func (r *TdxQuoteHdr) Finish(BinaryConvertible) error { return nil } // TdxQuoteReqABI is Linux's tdx-guest ABI for quote response type TdxQuoteReqABI struct { Buffer unsafe.Pointer Length uint64 } // TdxQuoteReq is Linux's tdx-guest ABI for TDX Report. The // types here enhance runtime safety when using Ioctl as an interface. type TdxQuoteReq struct { Buffer BinaryConvertible Length uint64 } // ABI returns the object itself. func (r *TdxQuoteReq) ABI() BinaryConversion { return &TdxQuoteReqABI{ Buffer: unsafe.Pointer(r.Buffer.ABI().Pointer()), Length: r.Length, } } // Pointer returns a pointer to the object itself. func (r *TdxQuoteReqABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) } // Finish is a no-op. func (r *TdxQuoteReqABI) Finish(b BinaryConvertible) error { _, ok := b.(*TdxQuoteReq) if !ok { return fmt.Errorf("Finish argument is %v. Expects a *TdxReportReq", reflect.TypeOf(b)) } 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-tdx-guest-0.3.1/go.mod000066400000000000000000000006251456371376300151720ustar00rootroot00000000000000module github.com/google/go-tdx-guest go 1.19 require ( github.com/google/go-cmp v0.5.7 github.com/google/go-configfs-tsm v0.2.2 github.com/google/go-sev-guest v0.8.0 github.com/google/logger v1.1.1 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.31.0 ) require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect go-tdx-guest-0.3.1/go.sum000066400000000000000000000043231456371376300152160ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/go-sev-guest v0.8.0 h1:IIZIqdcMJXgTm1nMvId442OUpYebbWDWa9bi9/lUUwc= github.com/google/go-sev-guest v0.8.0/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs= 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/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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= go-tdx-guest-0.3.1/pcs/000077500000000000000000000000001456371376300146465ustar00rootroot00000000000000go-tdx-guest-0.3.1/pcs/pcs.go000066400000000000000000000412301456371376300157620ustar00rootroot00000000000000// 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 pcs defines values specified for the Intel's Provisioning Certification Service package pcs import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/hex" "errors" "fmt" "strconv" "time" ) const ( pckCertExtensionSize = 6 sgxExtensionMinSize = 4 tcbExtensionSize = 18 ppidSize = 16 cpuSvnSize = 16 fmspcSize = 6 pceIDSize = 2 tcbComponentSize = 16 ) var ( pcsSgxBaseURL = "https://api.trustedservices.intel.com/sgx/certification/v4" pcsTdxBaseURL = "https://api.trustedservices.intel.com/tdx/certification/v4" sgxTcbComponentOidPrefix = []int{1, 2, 840, 113741, 1, 13, 1, 2} // OidSgxExtension is the x509v3 extension for PCK certificate's SGX Extension. OidSgxExtension = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1}) // OidPPID is the x509v3 extension for PCK certificate's SGX Extensions PPID value. OidPPID = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 1}) // OidTCB is the x509v3 extension for PCK certificate's SGX Extensions TCB struct. OidTCB = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 2}) // OidPCESvn is the x509v3 extension for PCK certificate's SGX Extensions PCESVN component in TCB struct. OidPCESvn = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 2, 17}) // OidCPUSvn is the x509v3 extension for PCK certificate's SGX Extensions CPUSVN component in TCB struct. OidCPUSvn = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 2, 18}) // OidPCEID is the x509v3 extension for PCK certificate's SGX Extensions PCEID value. OidPCEID = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 3}) // OidFMSPC is the x509v3 extension for PCK certificate's SGX Extensions FMSPC value. OidFMSPC = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 4}) // ErrPckExtInvalid error returned when parsing PCK certificate's extension returns leftover bytes ErrPckExtInvalid = errors.New("unexpected leftover bytes for PCK certificate's extension") // ErrTcbExtInvalid error returned when parsing of TCB in SGX Extension returns leftover bytes ErrTcbExtInvalid = errors.New("unexpected leftover bytes for TCB extension inside SGX extension field") // ErrTcbCompInvalid error returned when parsing of TCB components in SGX Extension returns leftover bytes ErrTcbCompInvalid = errors.New("unexpected leftover bytes for TCB components in TCB Extension inside SGX extension field") // ErrSgxExtInvalid error returned when parsing SGX extensions returns leftover bytes ErrSgxExtInvalid = errors.New("unexpected leftover bytes when parsing SGX extensions") ) // TdxTcbInfo struct is used to map response from tcbInfo PCS API Service type TdxTcbInfo struct { TcbInfo TcbInfo `json:"tcbInfo"` Signature string `json:"signature"` } // TcbInfo struct is used to map response from tcbInfo field type TcbInfo struct { ID string `json:"id"` Version byte `json:"version"` IssueDate time.Time `json:"issueDate"` NextUpdate time.Time `json:"nextUpdate"` Fmspc string `json:"fmspc"` PceID string `json:"pceId"` TcbType byte `json:"tcbType"` TcbEvaluationDataNumber int `json:"tcbEvaluationDataNumber"` TdxModule TdxModule `json:"tdxModule"` TdxModuleIdentities []TdxModuleIdentity `json:"tdxModuleIdentities"` TcbLevels []TcbLevel `json:"tcbLevels"` } // TdxModule struct is used to map response from tcbInfo for tdxModule field type TdxModule struct { Mrsigner HexBytes `json:"mrsigner"` Attributes HexBytes `json:"attributes"` AttributesMask HexBytes `json:"attributesMask"` } // TdxModuleIdentity struct is used to map response from tcbInfo for TdxModuleIdentity field type TdxModuleIdentity struct { ID string `json:"id"` Mrsigner HexBytes `json:"mrsigner"` Attributes HexBytes `json:"attributes"` AttributesMask HexBytes `json:"attributesMask"` TcbLevels []TcbLevel `json:"tcbLevels"` } // TcbLevel struct is used to map TCB Level field type TcbLevel struct { Tcb Tcb `json:"tcb"` TcbDate string `json:"tcbDate"` TcbStatus TcbComponentStatus `json:"tcbStatus"` AdvisoryIDs []string `json:"advisoryIDs"` } // Tcb struct is used to map TCB field type Tcb struct { SgxTcbcomponents []TcbComponent `json:"sgxtcbcomponents"` Pcesvn uint16 `json:"pcesvn"` TdxTcbcomponents []TcbComponent `json:"tdxtcbcomponents"` Isvsvn uint32 `json:"isvsvn"` } // TcbComponent struct is used to map sgx/tdx tcb components type TcbComponent struct { Svn byte `json:"svn"` Category string `json:"category"` Type string `json:"type"` } // QeIdentity struct is used to map response from enclaveIdentity PCS API Call type QeIdentity struct { EnclaveIdentity EnclaveIdentity `json:"enclaveIdentity"` Signature string `json:"signature"` } // EnclaveIdentity struct is used to map enclave identity field type EnclaveIdentity struct { ID string `json:"id"` Version byte `json:"version"` IssueDate time.Time `json:"issueDate"` NextUpdate time.Time `json:"nextUpdate"` TcbEvaluationDataNumber int `json:"tcbEvaluationDataNumber"` Miscselect HexBytes `json:"miscselect"` MiscselectMask HexBytes `json:"miscselectMask"` Attributes HexBytes `json:"attributes"` AttributesMask HexBytes `json:"attributesMask"` Mrsigner HexBytes `json:"mrsigner"` IsvProdID uint16 `json:"isvprodid"` TcbLevels []TcbLevel `json:"tcbLevels"` } // PckCertTCB represents struct that store information related to TCB components type PckCertTCB struct { PCESvn uint16 CPUSvn []byte CPUSvnComponents []byte } // PckExtensions represents the information stored in the x509 extensions of a PCK certificate which // will be required for verification type PckExtensions struct { PPID string TCB PckCertTCB PCEID string FMSPC string } // HexBytes struct contains hex decoded string to bytes value type HexBytes struct { Bytes []byte } // UnmarshalJSON for hex bytes converts hex encoded string to bytes func (hb *HexBytes) UnmarshalJSON(s []byte) error { unquoted, err := strconv.Unquote(string(s)) if err != nil { return err } val, err := hex.DecodeString(unquoted) if err != nil { return err } hb.Bytes = val return nil } // TcbComponentStatus represents the status of corresponding TCB field type TcbComponentStatus string const ( // TcbComponentStatusUpToDate denotes tcb status as UpToDate TcbComponentStatusUpToDate TcbComponentStatus = "UpToDate" // TcbComponentStatusSwHardeningNeeded denotes tcb status as SWHardeningNeeded TcbComponentStatusSwHardeningNeeded TcbComponentStatus = "SWHardeningNeeded" // TcbComponentStatusConfigurationNeeded denotes tcb status as ConfigurationNeeded TcbComponentStatusConfigurationNeeded TcbComponentStatus = "ConfigurationNeeded" // TcbComponentStatusConfigurationAndSWHardeningNeeded denotes tcb status as ConfigurationAndSWHardeningNeeded TcbComponentStatusConfigurationAndSWHardeningNeeded TcbComponentStatus = "ConfigurationAndSWHardeningNeeded" // TcbComponentStatusOutOfDate denotes tcb status as OutOfDate TcbComponentStatusOutOfDate TcbComponentStatus = "OutOfDate" // TcbComponentStatusOutOfDateConfigurationNeeded denotes tcb status as OutOfDateConfigurationNeeded TcbComponentStatusOutOfDateConfigurationNeeded TcbComponentStatus = "OutOfDateConfigurationNeeded" // TcbComponentStatusRevoked denotes tcb status as Revoked TcbComponentStatusRevoked TcbComponentStatus = "Revoked" ) // UnmarshalJSON for TcbComponentStatus maps tcb status to corresponding valid strings func (st *TcbComponentStatus) UnmarshalJSON(s []byte) error { unquotedStatus, err := strconv.Unquote(string(s)) if err != nil { return err } val := TcbComponentStatus(unquotedStatus) switch val { case TcbComponentStatusUpToDate, TcbComponentStatusSwHardeningNeeded, TcbComponentStatusConfigurationNeeded, TcbComponentStatusConfigurationAndSWHardeningNeeded, TcbComponentStatusOutOfDate, TcbComponentStatusOutOfDateConfigurationNeeded, TcbComponentStatusRevoked: *st = val default: return fmt.Errorf("unexpected tcb status found: %q", val) } return nil } func sgxTcbComponentOid(component int) asn1.ObjectIdentifier { return asn1.ObjectIdentifier(append(sgxTcbComponentOidPrefix, component)) } func asn1U8(ext *pkix.AttributeTypeAndValue, field string, out *byte) error { if ext == nil { return fmt.Errorf("no extension for field %s", field) } val, ok := ext.Value.(int64) if !ok { return fmt.Errorf("%s extension is of type %T, expected int64", field, ext.Value) } if val < 0 || val > 255 { return fmt.Errorf("int value for field %s isn't a byte: %d", field, val) } *out = byte(val) return nil } func asn1U16(ext *pkix.AttributeTypeAndValue, field string, out *uint16) error { if ext == nil { return fmt.Errorf("no extension for field %s", field) } val, ok := ext.Value.(int64) if !ok { return fmt.Errorf("%s extension is of type %T, expected int64", field, ext.Value) } if val < 0 || val > 65535 { return fmt.Errorf("int value for field %s isn't a uint16: %d", field, val) } *out = uint16(val) 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) } if len(ext.Value) == size { return ext.Value, nil } var octet []byte rest, err := asn1.Unmarshal(ext.Value, &octet) if err != nil { return nil, fmt.Errorf("could not parse %v extension as an octet string %v (value %v): %v", field, *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("%v extension's value size is %d, expected %d", field, len(octet), size) } return octet, nil } func extractTcbExtension(tcbExtension []asn1.RawValue, tcb *PckCertTCB) error { tcbComponents := make([]byte, tcbComponentSize) for _, ext := range tcbExtension { var tcbValue pkix.AttributeTypeAndValue rest, err := asn1.Unmarshal(ext.FullBytes, &tcbValue) if err != nil { return fmt.Errorf("could not parse TCB component inside the TCB extension in the PCK certificate: %v", err) } if len(rest) != 0 { return ErrTcbCompInvalid } for i := 0; i < tcbComponentSize; i++ { tcbComponentOid := sgxTcbComponentOid(i + 1) if tcbValue.Type.Equal(tcbComponentOid) { phrase := fmt.Sprintf("sgxTcbComponent%d", i+1) var val byte if err := asn1U8(&tcbValue, phrase, &val); err != nil { return err } tcbComponents[i] = val break } } if tcbValue.Type.Equal(OidPCESvn) { if err := asn1U16(&tcbValue, "PCESvn", &tcb.PCESvn); err != nil { return err } } if tcbValue.Type.Equal(OidCPUSvn) { val, ok := tcbValue.Value.([]byte) if !ok { return fmt.Errorf("CPUSVN component in TCB extension is of type %T, expected []byte", tcbValue.Value) } if len(tcbValue.Value.([]byte)) != cpuSvnSize { return fmt.Errorf("CPUSVN component in TCB extension is of size %d, expected %d", len(tcbValue.Value.([]byte)), cpuSvnSize) } tcb.CPUSvn = val } } tcb.CPUSvnComponents = tcbComponents return nil } func extractAsn1SequenceTcbExtension(ext asn1.RawValue) (*PckCertTCB, error) { tcb := &PckCertTCB{} var sExtension []asn1.RawValue rest, err := asn1.Unmarshal(ext.FullBytes, &sExtension) if err != nil { return nil, fmt.Errorf("could not parse TCB extension present inside the SGX extension in PCK certificate: %v", err) } if len(rest) != 0 { return nil, ErrTcbExtInvalid } if len(sExtension) != 2 { return nil, fmt.Errorf("TCB extension when unmarshalled is of size %d, expected 2", len(sExtension)) } var tcbExtension []asn1.RawValue rest, err = asn1.Unmarshal(sExtension[1].FullBytes, &tcbExtension) if err != nil { return nil, fmt.Errorf("could not parse TCB components present inside the SGX extension in PCK certificate: %v", err) } if len(rest) != 0 { return nil, ErrTcbCompInvalid } if len(tcbExtension) != tcbExtensionSize { return nil, fmt.Errorf("TCB extension is of size %d, expected %d", len(tcbExtension), tcbExtensionSize) } if err := extractTcbExtension(tcbExtension, tcb); err != nil { return nil, err } return tcb, nil } func extractAsn1OctetStringExtension(name string, extension asn1.RawValue, size int) (string, error) { var sExtension pkix.Extension rest, err := asn1.Unmarshal(extension.FullBytes, &sExtension) if err != nil { return "", fmt.Errorf("could not parse %s present inside the SGX extension in PCK certificate: %v", name, err) } if len(rest) != 0 { return "", fmt.Errorf("unexpected leftover bytes for %s extension inside SGX extension field", name) } val, err := asn1OctetString(&sExtension, name, size) if err != nil { return "", err } return hex.EncodeToString(val), nil } func extractSgxExtensions(extensions []asn1.RawValue) (*PckExtensions, error) { pckExtension := &PckExtensions{} if len(extensions) < sgxExtensionMinSize { return nil, fmt.Errorf("SGX Extension has length %d. It should have a minimum length of %d", len(extensions), sgxExtensionMinSize) } for i, ext := range extensions { var sExtension pkix.AttributeTypeAndValue rest, err := asn1.Unmarshal(ext.FullBytes, &sExtension) if err != nil { return nil, fmt.Errorf("could not parse SGX extension's in PCK certificate: %v", err) } if len(rest) != 0 { return nil, ErrSgxExtInvalid } if sExtension.Type.Equal(OidPPID) { pckExtension.PPID, err = extractAsn1OctetStringExtension("PPID", extensions[i], ppidSize) if err != nil { return nil, err } } if sExtension.Type.Equal(OidTCB) { tcb, err := extractAsn1SequenceTcbExtension(extensions[i]) if err != nil { return nil, err } pckExtension.TCB = *tcb } if sExtension.Type.Equal(OidPCEID) { pckExtension.PCEID, err = extractAsn1OctetStringExtension("PCEID", extensions[i], pceIDSize) if err != nil { return nil, err } } if sExtension.Type.Equal(OidFMSPC) { pckExtension.FMSPC, err = extractAsn1OctetStringExtension("FMSPC", extensions[i], fmspcSize) if err != nil { return nil, err } } } return pckExtension, nil } func findMatchingExtension(extns []pkix.Extension, oid asn1.ObjectIdentifier) (*pkix.Extension, error) { for _, ext := range extns { if ext.Id.Equal(oid) { return &ext, nil } } return nil, fmt.Errorf("unable to find extension with OID %v in PCK Certificate", oid) } // PckCertificateExtensions returns only those x509v3 extensions from the PCK certificate into a // struct type which will be required in verification purpose. func PckCertificateExtensions(cert *x509.Certificate) (*PckExtensions, error) { if len(cert.Extensions) != pckCertExtensionSize { return nil, fmt.Errorf("PCK certificate extensions length found %d. Expected %d", len(cert.Extensions), pckCertExtensionSize) } sgxExt, err := findMatchingExtension(cert.Extensions, OidSgxExtension) if err != nil { return nil, fmt.Errorf("could not find SGX extension present in the PCK certificate: %v", err) } var sgxExtensions []asn1.RawValue rest, err := asn1.Unmarshal(sgxExt.Value, &sgxExtensions) if err != nil { return nil, fmt.Errorf("could not parse SGX extension present in the PCK certificate: %v", err) } if len(rest) != 0 { return nil, ErrPckExtInvalid } return extractSgxExtensions(sgxExtensions) } // PckCrlURL returns the Intel PCS URL for retrieving PCK CRL func PckCrlURL(ca string) string { return fmt.Sprintf("%s/pckcrl?ca=%s&encoding=der", pcsSgxBaseURL, ca) } // TcbInfoURL returns the Intel PCS URL for retrieving TCB Info func TcbInfoURL(fmspc string) string { return fmt.Sprintf("%s/tcb?fmspc=%s", pcsTdxBaseURL, fmspc) } // QeIdentityURL returns the Intel PCS URL for retrieving QE identity func QeIdentityURL() string { return fmt.Sprintf("%s/qe/identity", pcsTdxBaseURL) } go-tdx-guest-0.3.1/pcs/pcs_test.go000066400000000000000000000024611456371376300170240ustar00rootroot00000000000000// 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 pcs import ( "encoding/hex" "testing" ) func TestPckCrlURL(t *testing.T) { want := pcsSgxBaseURL + "/pckcrl?ca=platform&encoding=der" if got := PckCrlURL("platform"); got != want { t.Errorf(`PckCrlURL("platform") = %q. Expected %q`, got, want) } } func TestTcbInfoURL(t *testing.T) { want := pcsTdxBaseURL + "/tcb?fmspc=50806f000000" fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) if got := TcbInfoURL(fmspc); got != want { t.Errorf("TcbInfoURL(%q) = %q. Expected %q", fmspc, got, want) } } func TestQeIdentityURL(t *testing.T) { want := pcsTdxBaseURL + "/qe/identity" if got := QeIdentityURL(); got != want { t.Errorf("QEIdentityURL() = %q. Expected %q", got, want) } } go-tdx-guest-0.3.1/proto/000077500000000000000000000000001456371376300152245ustar00rootroot00000000000000go-tdx-guest-0.3.1/proto/checkconfig.proto000066400000000000000000000061741456371376300205640ustar00rootroot00000000000000// 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. syntax = "proto3"; // Package checkconfig represents an attestation validation policy. package checkconfig; option go_package = "github.com/google/go-tdx-guest/proto/checkconfig"; // Policy is a representation of an attestation quote 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 { // HeaderPolicy is representation of Header of an attestation quote validation // policy. HeaderPolicy header_policy = 1; // should be 20 bytes // TDQuoteBodyPolicy is representation of TdQuoteBody of an attestation quote // validation policy. TDQuoteBodyPolicy td_quote_body_policy = 2; // should be 528 bytes } message HeaderPolicy { uint32 minimum_qe_svn = 1; // should not exceed uint16 max uint32 minimum_pce_svn = 2; // should not exceed uint16 max // Unique vendor id of QE vendor bytes qe_vendor_id = 3; // should be 16 bytes } message TDQuoteBodyPolicy { bytes minimum_tee_tcb_svn = 1; // should be 16 bytes bytes mr_seam = 2; // should be 48 bytes bytes td_attributes = 3; // should be 8 bytes bytes xfam = 4; // should be 8 bytes bytes mr_td = 5; // should be 48 bytes bytes mr_config_id = 6; // should be 48 bytes bytes mr_owner = 7; // should be 48 bytes bytes mr_owner_config = 8; // should be 48 bytes repeated bytes rtmrs = 9; // should be 48 * rtmrsCount bytes report_data = 10; // should be 64 bytes } // RootOfTrust represents configuration for which hardware root of trust // certificates to use for verifying attestation quote. message RootOfTrust { // Paths to CA bundles for the Intel TDX. // Must be in PEM format. // If empty, uses the verification library's embedded certificates from Intel. repeated string cabundle_paths = 1; // PEM format CA bundles for Intel TDX. Combined with contents of // cabundle_paths. repeated string cabundles = 2; // If true, download and check the CRL for revoked certificates. bool check_crl = 3; // If true, then check is not permitted to download necessary files for // verification. bool get_collateral = 4; } // 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 { // The report validation policy. Policy policy = 1; // Configures which hardware keys to trust. Default uses library-embedded // certificate. RootOfTrust root_of_trust = 2; } go-tdx-guest-0.3.1/proto/checkconfig/000077500000000000000000000000001456371376300174675ustar00rootroot00000000000000go-tdx-guest-0.3.1/proto/checkconfig/checkconfig.pb.go000066400000000000000000000531051456371376300226650ustar00rootroot00000000000000// 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.21.12 // source: checkconfig.proto // Package checkconfig represents an attestation validation policy. package checkconfig 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) ) // Policy is a representation of an attestation quote 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 // HeaderPolicy is representation of Header of an attestation quote validation // policy. HeaderPolicy *HeaderPolicy `protobuf:"bytes,1,opt,name=header_policy,json=headerPolicy,proto3" json:"header_policy,omitempty"` // should be 20 bytes // TDQuoteBodyPolicy is representation of TdQuoteBody of an attestation quote // validation policy. TdQuoteBodyPolicy *TDQuoteBodyPolicy `protobuf:"bytes,2,opt,name=td_quote_body_policy,json=tdQuoteBodyPolicy,proto3" json:"td_quote_body_policy,omitempty"` // should be 528 bytes } func (x *Policy) Reset() { *x = Policy{} if protoimpl.UnsafeEnabled { mi := &file_checkconfig_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_checkconfig_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_checkconfig_proto_rawDescGZIP(), []int{0} } func (x *Policy) GetHeaderPolicy() *HeaderPolicy { if x != nil { return x.HeaderPolicy } return nil } func (x *Policy) GetTdQuoteBodyPolicy() *TDQuoteBodyPolicy { if x != nil { return x.TdQuoteBodyPolicy } return nil } type HeaderPolicy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields MinimumQeSvn uint32 `protobuf:"varint,1,opt,name=minimum_qe_svn,json=minimumQeSvn,proto3" json:"minimum_qe_svn,omitempty"` // should not exceed uint16 max MinimumPceSvn uint32 `protobuf:"varint,2,opt,name=minimum_pce_svn,json=minimumPceSvn,proto3" json:"minimum_pce_svn,omitempty"` // should not exceed uint16 max // Unique vendor id of QE vendor QeVendorId []byte `protobuf:"bytes,3,opt,name=qe_vendor_id,json=qeVendorId,proto3" json:"qe_vendor_id,omitempty"` // should be 16 bytes } func (x *HeaderPolicy) Reset() { *x = HeaderPolicy{} if protoimpl.UnsafeEnabled { mi := &file_checkconfig_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HeaderPolicy) String() string { return protoimpl.X.MessageStringOf(x) } func (*HeaderPolicy) ProtoMessage() {} func (x *HeaderPolicy) ProtoReflect() protoreflect.Message { mi := &file_checkconfig_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 HeaderPolicy.ProtoReflect.Descriptor instead. func (*HeaderPolicy) Descriptor() ([]byte, []int) { return file_checkconfig_proto_rawDescGZIP(), []int{1} } func (x *HeaderPolicy) GetMinimumQeSvn() uint32 { if x != nil { return x.MinimumQeSvn } return 0 } func (x *HeaderPolicy) GetMinimumPceSvn() uint32 { if x != nil { return x.MinimumPceSvn } return 0 } func (x *HeaderPolicy) GetQeVendorId() []byte { if x != nil { return x.QeVendorId } return nil } type TDQuoteBodyPolicy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields MinimumTeeTcbSvn []byte `protobuf:"bytes,1,opt,name=minimum_tee_tcb_svn,json=minimumTeeTcbSvn,proto3" json:"minimum_tee_tcb_svn,omitempty"` // should be 16 bytes MrSeam []byte `protobuf:"bytes,2,opt,name=mr_seam,json=mrSeam,proto3" json:"mr_seam,omitempty"` // should be 48 bytes TdAttributes []byte `protobuf:"bytes,3,opt,name=td_attributes,json=tdAttributes,proto3" json:"td_attributes,omitempty"` // should be 8 bytes Xfam []byte `protobuf:"bytes,4,opt,name=xfam,proto3" json:"xfam,omitempty"` // should be 8 bytes MrTd []byte `protobuf:"bytes,5,opt,name=mr_td,json=mrTd,proto3" json:"mr_td,omitempty"` // should be 48 bytes MrConfigId []byte `protobuf:"bytes,6,opt,name=mr_config_id,json=mrConfigId,proto3" json:"mr_config_id,omitempty"` // should be 48 bytes MrOwner []byte `protobuf:"bytes,7,opt,name=mr_owner,json=mrOwner,proto3" json:"mr_owner,omitempty"` // should be 48 bytes MrOwnerConfig []byte `protobuf:"bytes,8,opt,name=mr_owner_config,json=mrOwnerConfig,proto3" json:"mr_owner_config,omitempty"` // should be 48 bytes Rtmrs [][]byte `protobuf:"bytes,9,rep,name=rtmrs,proto3" json:"rtmrs,omitempty"` // should be 48 * rtmrsCount ReportData []byte `protobuf:"bytes,10,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // should be 64 bytes } func (x *TDQuoteBodyPolicy) Reset() { *x = TDQuoteBodyPolicy{} if protoimpl.UnsafeEnabled { mi := &file_checkconfig_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TDQuoteBodyPolicy) String() string { return protoimpl.X.MessageStringOf(x) } func (*TDQuoteBodyPolicy) ProtoMessage() {} func (x *TDQuoteBodyPolicy) ProtoReflect() protoreflect.Message { mi := &file_checkconfig_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 TDQuoteBodyPolicy.ProtoReflect.Descriptor instead. func (*TDQuoteBodyPolicy) Descriptor() ([]byte, []int) { return file_checkconfig_proto_rawDescGZIP(), []int{2} } func (x *TDQuoteBodyPolicy) GetMinimumTeeTcbSvn() []byte { if x != nil { return x.MinimumTeeTcbSvn } return nil } func (x *TDQuoteBodyPolicy) GetMrSeam() []byte { if x != nil { return x.MrSeam } return nil } func (x *TDQuoteBodyPolicy) GetTdAttributes() []byte { if x != nil { return x.TdAttributes } return nil } func (x *TDQuoteBodyPolicy) GetXfam() []byte { if x != nil { return x.Xfam } return nil } func (x *TDQuoteBodyPolicy) GetMrTd() []byte { if x != nil { return x.MrTd } return nil } func (x *TDQuoteBodyPolicy) GetMrConfigId() []byte { if x != nil { return x.MrConfigId } return nil } func (x *TDQuoteBodyPolicy) GetMrOwner() []byte { if x != nil { return x.MrOwner } return nil } func (x *TDQuoteBodyPolicy) GetMrOwnerConfig() []byte { if x != nil { return x.MrOwnerConfig } return nil } func (x *TDQuoteBodyPolicy) GetRtmrs() [][]byte { if x != nil { return x.Rtmrs } return nil } func (x *TDQuoteBodyPolicy) GetReportData() []byte { if x != nil { return x.ReportData } return nil } // RootOfTrust represents configuration for which hardware root of trust // certificates to use for verifying attestation quote. type RootOfTrust struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Paths to CA bundles for the Intel TDX. // Must be in PEM format. // If empty, uses the verification library's embedded certificates from Intel. CabundlePaths []string `protobuf:"bytes,1,rep,name=cabundle_paths,json=cabundlePaths,proto3" json:"cabundle_paths,omitempty"` // PEM format CA bundles for Intel TDX. Combined with contents of // cabundle_paths. Cabundles []string `protobuf:"bytes,2,rep,name=cabundles,proto3" json:"cabundles,omitempty"` // If true, download and check the CRL for revoked certificates. CheckCrl bool `protobuf:"varint,3,opt,name=check_crl,json=checkCrl,proto3" json:"check_crl,omitempty"` // If true, then check is not permitted to download necessary files for // verification. GetCollateral bool `protobuf:"varint,4,opt,name=get_collateral,json=getCollateral,proto3" json:"get_collateral,omitempty"` } func (x *RootOfTrust) Reset() { *x = RootOfTrust{} if protoimpl.UnsafeEnabled { mi := &file_checkconfig_proto_msgTypes[3] 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_checkconfig_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 RootOfTrust.ProtoReflect.Descriptor instead. func (*RootOfTrust) Descriptor() ([]byte, []int) { return file_checkconfig_proto_rawDescGZIP(), []int{3} } 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) GetGetCollateral() bool { if x != nil { return x.GetCollateral } return false } // 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 // The report validation policy. Policy *Policy `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` // Configures which hardware keys to trust. Default uses library-embedded // certificate. RootOfTrust *RootOfTrust `protobuf:"bytes,2,opt,name=root_of_trust,json=rootOfTrust,proto3" json:"root_of_trust,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_checkconfig_proto_msgTypes[4] 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_checkconfig_proto_msgTypes[4] 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_checkconfig_proto_rawDescGZIP(), []int{4} } func (x *Config) GetPolicy() *Policy { if x != nil { return x.Policy } return nil } func (x *Config) GetRootOfTrust() *RootOfTrust { if x != nil { return x.RootOfTrust } return nil } var File_checkconfig_proto protoreflect.FileDescriptor var file_checkconfig_proto_rawDesc = []byte{ 0x0a, 0x11, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x99, 0x01, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x3e, 0x0a, 0x0d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x4f, 0x0a, 0x14, 0x74, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x44, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x11, 0x74, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x7e, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x71, 0x65, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x51, 0x65, 0x53, 0x76, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x63, 0x65, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x63, 0x65, 0x53, 0x76, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x71, 0x65, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x71, 0x65, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x22, 0xc5, 0x02, 0x0a, 0x11, 0x54, 0x44, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x74, 0x65, 0x65, 0x5f, 0x74, 0x63, 0x62, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x54, 0x65, 0x65, 0x54, 0x63, 0x62, 0x53, 0x76, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x72, 0x5f, 0x73, 0x65, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6d, 0x72, 0x53, 0x65, 0x61, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x74, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x66, 0x61, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x78, 0x66, 0x61, 0x6d, 0x12, 0x13, 0x0a, 0x05, 0x6d, 0x72, 0x5f, 0x74, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6d, 0x72, 0x54, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x72, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x72, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x72, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x72, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x74, 0x6d, 0x72, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x74, 0x6d, 0x72, 0x73, 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, 0x22, 0x96, 0x01, 0x0a, 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x4f, 0x66, 0x54, 0x72, 0x75, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x61, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 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, 0x02, 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, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x72, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x22, 0x73, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x3c, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x74, 0x64, 0x78, 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_checkconfig_proto_rawDescOnce sync.Once file_checkconfig_proto_rawDescData = file_checkconfig_proto_rawDesc ) func file_checkconfig_proto_rawDescGZIP() []byte { file_checkconfig_proto_rawDescOnce.Do(func() { file_checkconfig_proto_rawDescData = protoimpl.X.CompressGZIP(file_checkconfig_proto_rawDescData) }) return file_checkconfig_proto_rawDescData } var file_checkconfig_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_checkconfig_proto_goTypes = []interface{}{ (*Policy)(nil), // 0: checkconfig.Policy (*HeaderPolicy)(nil), // 1: checkconfig.HeaderPolicy (*TDQuoteBodyPolicy)(nil), // 2: checkconfig.TDQuoteBodyPolicy (*RootOfTrust)(nil), // 3: checkconfig.RootOfTrust (*Config)(nil), // 4: checkconfig.Config } var file_checkconfig_proto_depIdxs = []int32{ 1, // 0: checkconfig.Policy.header_policy:type_name -> checkconfig.HeaderPolicy 2, // 1: checkconfig.Policy.td_quote_body_policy:type_name -> checkconfig.TDQuoteBodyPolicy 0, // 2: checkconfig.Config.policy:type_name -> checkconfig.Policy 3, // 3: checkconfig.Config.root_of_trust:type_name -> checkconfig.RootOfTrust 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name 4, // [4:4] is the sub-list for extension extendee 0, // [0:4] is the sub-list for field type_name } func init() { file_checkconfig_proto_init() } func file_checkconfig_proto_init() { if File_checkconfig_proto != nil { return } if !protoimpl.UnsafeEnabled { file_checkconfig_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_checkconfig_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HeaderPolicy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_checkconfig_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TDQuoteBodyPolicy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_checkconfig_proto_msgTypes[3].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_checkconfig_proto_msgTypes[4].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_checkconfig_proto_rawDesc, NumEnums: 0, NumMessages: 5, NumExtensions: 0, NumServices: 0, }, GoTypes: file_checkconfig_proto_goTypes, DependencyIndexes: file_checkconfig_proto_depIdxs, MessageInfos: file_checkconfig_proto_msgTypes, }.Build() File_checkconfig_proto = out.File file_checkconfig_proto_rawDesc = nil file_checkconfig_proto_goTypes = nil file_checkconfig_proto_depIdxs = nil } go-tdx-guest-0.3.1/proto/checkconfig/doc.go000066400000000000000000000012561456371376300205670ustar00rootroot00000000000000// 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 checkconfig defines the message type for the check CLI tool's options. package checkconfig go-tdx-guest-0.3.1/proto/doc.go000066400000000000000000000041371456371376300163250ustar00rootroot00000000000000// 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 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-tdx-guest/proto tdx.proto //go:generate protoc -I$PROTOC_INSTALL_DIR/include -I=. --go_out=. --go_opt=module=github.com/google/go-tdx-guest/proto checkconfig.proto go-tdx-guest-0.3.1/proto/tdx.proto000066400000000000000000000130611456371376300171110ustar00rootroot00000000000000// 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. syntax = "proto3"; // Package tdx represents a TDX attestation quote. package tdx; option go_package = "github.com/google/go-tdx-guest/proto/tdx"; message QuoteV4 { // Header of quote structure Header header = 1; // should be 48 bytes // TD report by which quote is generated TDQuoteBody td_quote_body = 2; // should be 584 bytes // Size of the Quote Signature Data structure uint32 signed_data_size = 3; // Quote Signature Data structure. Ecdsa256BitQuoteV4AuthData signed_data = 4; // The size should be same as signed_data_size // Extra bytes included to fill the quote buffer bytes extra_bytes = 5; // These trailing bytes can be ignored } message Header { // Version 4 supported uint32 version = 1; // Type of attestation key used by quoting enclave // Values : // 2 (ECDSA-256-with-P-256 curve) // 3 (ECDSA-384-with-P-384 curve) (Currently not supported) uint32 attestation_key_type = 2; // TEE for this attestation // TDX : 0x00000081 uint32 tee_type = 3; bytes qe_svn = 4; // should be 2 bytes bytes pce_svn = 5; // should be 2 bytes // Unique vendor id of QE vendor bytes qe_vendor_id = 6; // should be 16 bytes // Custom user defined data bytes user_data = 7; // should be 20 bytes } message TDQuoteBody { bytes tee_tcb_svn = 1; // should be 16 bytes bytes mr_seam = 2; // should be 48 bytes bytes mr_signer_seam = 3; // should be 48 bytes bytes seam_attributes = 4; // should be 8 bytes bytes td_attributes = 5; // should be 8 bytes bytes xfam = 6; // should be 8 bytes bytes mr_td = 7; // should be 48 bytes bytes mr_config_id = 8; // should be 48 bytes bytes mr_owner = 9; // should be 48 bytes bytes mr_owner_config = 10; // should be 48 bytes repeated bytes rtmrs = 11; // should be 48 * rtmrsCount bytes report_data = 12; // should be 64 bytes } message Ecdsa256BitQuoteV4AuthData { // The ECDSA 256-bit signature. bytes signature = 1; // should be 64 bytes // The ECDSA 256-bit public key of the attestation key. bytes ecdsa_attestation_key = 2; // should be 64 bytes // The certification data. CertificationData certification_data = 3; } message CertificationData { // Supported values: // - 1 (PCK identifier: PPID in plain text, CPUSVN and PCESVN) // - 2 (PCK identifier: PPID encrypted using RSA-2048-OAEP, CPUSVN and PCESVN) // - 3 (PCK identifier: PPID encrypted using RSA-3072-OAEP, CPUSVN and PCESVN) // - 4 (PCK Leaf Certificate in plain text, currently not supported) // - 5 (Concatenated PCK Cert Chain) // - 6 (QE Report Certification Data) // - 7 (PLATFORM_MANIFEST, currently not supported) uint32 certificate_data_type = 1; // Size of Certification Data field uint32 size = 2; // Certification Data Type: // - 1: Byte array that contains concatenation of PPID, CPUSVN,PCESVN (LE), // PCEID (LE). // - 2: Byte array that contains concatenation of PPID encrypted using // RSA-2048-OAEP, CPUSVN, PCESVN (LE), PCEID (LE). // - 3: Byte array that contains concatenation of PPID encrypted using // RSA-3072-OAEP, CPUSVN, PCESVN (LE), PCEID (LE). // - 4: PCK Leaf Certificate // - 5: Concatenated PCK Cert Chain (PEM formatted). PCK LeafCert|| // Intermediate CA Cert|| Root CA Cert //- 6: QE Report Certification Data //- 7: PLATFORM_MANIFEST (currently not supported) QEReportCertificationData qe_report_certification_data = 3; } message QEReportCertificationData { EnclaveReport qe_report = 1; bytes qe_report_signature = 2; // should be 64 bytes QeAuthData qe_auth_data = 3; PCKCertificateChainData pck_certificate_chain_data = 4; } message PCKCertificateChainData { // Supported values: // - 1 (PCK identifier: PPID in plain text, CPUSVN and PCESVN) // - 2 (PCK identifier: PPID encrypted using RSA-2048-OAEP, CPUSVN and PCESVN) // - 3 (PCK identifier: PPID encrypted using RSA-3072-OAEP, CPUSVN and PCESVN) // - 4 (PCK Leaf Certificate in plain text, currently not supported) // - 5 (Concatenated PCK Cert Chain) // - 6 (QE Report Certification Data) // - 7 (PLATFORM_MANIFEST, currently not supported) uint32 certificate_data_type = 1; // Size of Certification Data field uint32 size = 2; bytes pck_cert_chain = 3; } message QeAuthData { // The parsed data size. uint32 parsed_data_size = 1; // should be 2 bytes // The data. bytes data = 2; } message EnclaveReport { bytes cpu_svn = 1; // should be 16 bytes uint32 misc_select = 2; // should be 4 bytes bytes reserved1 = 3; // should be 28 bytes bytes attributes = 4; // should be 16 bytes bytes mr_enclave = 5; // should be 32 bytes bytes reserved2 = 6; // should be 32 bytes bytes mr_signer = 7; // should be 32 bytes bytes reserved3 = 8; // should be 96 bytes uint32 isv_prod_id = 9; // should be 2 bytes uint32 isv_svn = 10; // should be 2 bytes bytes reserved4 = 11; // should be 60 bytes bytes report_data = 12; // should be 64 bytes } go-tdx-guest-0.3.1/proto/tdx/000077500000000000000000000000001456371376300160235ustar00rootroot00000000000000go-tdx-guest-0.3.1/proto/tdx/doc.go000066400000000000000000000012431456371376300171170ustar00rootroot00000000000000// 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 tdx implements a protocol buffer for representing TDX attestations. package tdx go-tdx-guest-0.3.1/proto/tdx/tdx.pb.go000066400000000000000000001226011456371376300175530ustar00rootroot00000000000000// 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.21.12 // source: tdx.proto // Package tdx represents a TDX attestation quote. package tdx 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) ) type QuoteV4 struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Header of quote structure Header *Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` // should be 48 bytes // TD report by which quote is generated TdQuoteBody *TDQuoteBody `protobuf:"bytes,2,opt,name=td_quote_body,json=tdQuoteBody,proto3" json:"td_quote_body,omitempty"` // should be 584 bytes // Size of the Quote Signature Data structure SignedDataSize uint32 `protobuf:"varint,3,opt,name=signed_data_size,json=signedDataSize,proto3" json:"signed_data_size,omitempty"` // Quote Signature Data structure. SignedData *Ecdsa256BitQuoteV4AuthData `protobuf:"bytes,4,opt,name=signed_data,json=signedData,proto3" json:"signed_data,omitempty"` // The size should be same as signed_data_size // Extra bytes included to fill the quote buffer ExtraBytes []byte `protobuf:"bytes,5,opt,name=extra_bytes,json=extraBytes,proto3" json:"extra_bytes,omitempty"` // These trailing bytes can be ignored } func (x *QuoteV4) Reset() { *x = QuoteV4{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QuoteV4) String() string { return protoimpl.X.MessageStringOf(x) } func (*QuoteV4) ProtoMessage() {} func (x *QuoteV4) ProtoReflect() protoreflect.Message { mi := &file_tdx_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 QuoteV4.ProtoReflect.Descriptor instead. func (*QuoteV4) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{0} } func (x *QuoteV4) GetHeader() *Header { if x != nil { return x.Header } return nil } func (x *QuoteV4) GetTdQuoteBody() *TDQuoteBody { if x != nil { return x.TdQuoteBody } return nil } func (x *QuoteV4) GetSignedDataSize() uint32 { if x != nil { return x.SignedDataSize } return 0 } func (x *QuoteV4) GetSignedData() *Ecdsa256BitQuoteV4AuthData { if x != nil { return x.SignedData } return nil } func (x *QuoteV4) GetExtraBytes() []byte { if x != nil { return x.ExtraBytes } return nil } type Header struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Version 4 supported Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` // Type of attestation key used by quoting enclave // Values : // 2 (ECDSA-256-with-P-256 curve) // 3 (ECDSA-384-with-P-384 curve) (Currently not supported) AttestationKeyType uint32 `protobuf:"varint,2,opt,name=attestation_key_type,json=attestationKeyType,proto3" json:"attestation_key_type,omitempty"` // TEE for this attestation // TDX : 0x00000081 TeeType uint32 `protobuf:"varint,3,opt,name=tee_type,json=teeType,proto3" json:"tee_type,omitempty"` QeSvn []byte `protobuf:"bytes,4,opt,name=qe_svn,json=qeSvn,proto3" json:"qe_svn,omitempty"` // should be 2 bytes PceSvn []byte `protobuf:"bytes,5,opt,name=pce_svn,json=pceSvn,proto3" json:"pce_svn,omitempty"` // should be 2 bytes // Unique vendor id of QE vendor QeVendorId []byte `protobuf:"bytes,6,opt,name=qe_vendor_id,json=qeVendorId,proto3" json:"qe_vendor_id,omitempty"` // should be 16 bytes // Custom user defined data UserData []byte `protobuf:"bytes,7,opt,name=user_data,json=userData,proto3" json:"user_data,omitempty"` // should be 20 bytes } func (x *Header) Reset() { *x = Header{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Header) String() string { return protoimpl.X.MessageStringOf(x) } func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { mi := &file_tdx_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 Header.ProtoReflect.Descriptor instead. func (*Header) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{1} } func (x *Header) GetVersion() uint32 { if x != nil { return x.Version } return 0 } func (x *Header) GetAttestationKeyType() uint32 { if x != nil { return x.AttestationKeyType } return 0 } func (x *Header) GetTeeType() uint32 { if x != nil { return x.TeeType } return 0 } func (x *Header) GetQeSvn() []byte { if x != nil { return x.QeSvn } return nil } func (x *Header) GetPceSvn() []byte { if x != nil { return x.PceSvn } return nil } func (x *Header) GetQeVendorId() []byte { if x != nil { return x.QeVendorId } return nil } func (x *Header) GetUserData() []byte { if x != nil { return x.UserData } return nil } type TDQuoteBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TeeTcbSvn []byte `protobuf:"bytes,1,opt,name=tee_tcb_svn,json=teeTcbSvn,proto3" json:"tee_tcb_svn,omitempty"` // should be 16 bytes MrSeam []byte `protobuf:"bytes,2,opt,name=mr_seam,json=mrSeam,proto3" json:"mr_seam,omitempty"` // should be 48 bytes MrSignerSeam []byte `protobuf:"bytes,3,opt,name=mr_signer_seam,json=mrSignerSeam,proto3" json:"mr_signer_seam,omitempty"` // should be 48 bytes SeamAttributes []byte `protobuf:"bytes,4,opt,name=seam_attributes,json=seamAttributes,proto3" json:"seam_attributes,omitempty"` // should be 8 bytes TdAttributes []byte `protobuf:"bytes,5,opt,name=td_attributes,json=tdAttributes,proto3" json:"td_attributes,omitempty"` // should be 8 bytes Xfam []byte `protobuf:"bytes,6,opt,name=xfam,proto3" json:"xfam,omitempty"` // should be 8 bytes MrTd []byte `protobuf:"bytes,7,opt,name=mr_td,json=mrTd,proto3" json:"mr_td,omitempty"` // should be 48 bytes MrConfigId []byte `protobuf:"bytes,8,opt,name=mr_config_id,json=mrConfigId,proto3" json:"mr_config_id,omitempty"` // should be 48 bytes MrOwner []byte `protobuf:"bytes,9,opt,name=mr_owner,json=mrOwner,proto3" json:"mr_owner,omitempty"` // should be 48 bytes MrOwnerConfig []byte `protobuf:"bytes,10,opt,name=mr_owner_config,json=mrOwnerConfig,proto3" json:"mr_owner_config,omitempty"` // should be 48 bytes Rtmrs [][]byte `protobuf:"bytes,11,rep,name=rtmrs,proto3" json:"rtmrs,omitempty"` // should be 48 * rtmrsCount ReportData []byte `protobuf:"bytes,12,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // should be 64 bytes } func (x *TDQuoteBody) Reset() { *x = TDQuoteBody{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TDQuoteBody) String() string { return protoimpl.X.MessageStringOf(x) } func (*TDQuoteBody) ProtoMessage() {} func (x *TDQuoteBody) ProtoReflect() protoreflect.Message { mi := &file_tdx_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 TDQuoteBody.ProtoReflect.Descriptor instead. func (*TDQuoteBody) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{2} } func (x *TDQuoteBody) GetTeeTcbSvn() []byte { if x != nil { return x.TeeTcbSvn } return nil } func (x *TDQuoteBody) GetMrSeam() []byte { if x != nil { return x.MrSeam } return nil } func (x *TDQuoteBody) GetMrSignerSeam() []byte { if x != nil { return x.MrSignerSeam } return nil } func (x *TDQuoteBody) GetSeamAttributes() []byte { if x != nil { return x.SeamAttributes } return nil } func (x *TDQuoteBody) GetTdAttributes() []byte { if x != nil { return x.TdAttributes } return nil } func (x *TDQuoteBody) GetXfam() []byte { if x != nil { return x.Xfam } return nil } func (x *TDQuoteBody) GetMrTd() []byte { if x != nil { return x.MrTd } return nil } func (x *TDQuoteBody) GetMrConfigId() []byte { if x != nil { return x.MrConfigId } return nil } func (x *TDQuoteBody) GetMrOwner() []byte { if x != nil { return x.MrOwner } return nil } func (x *TDQuoteBody) GetMrOwnerConfig() []byte { if x != nil { return x.MrOwnerConfig } return nil } func (x *TDQuoteBody) GetRtmrs() [][]byte { if x != nil { return x.Rtmrs } return nil } func (x *TDQuoteBody) GetReportData() []byte { if x != nil { return x.ReportData } return nil } type Ecdsa256BitQuoteV4AuthData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The ECDSA 256-bit signature. Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` // should be 64 bytes // The ECDSA 256-bit public key of the attestation key. EcdsaAttestationKey []byte `protobuf:"bytes,2,opt,name=ecdsa_attestation_key,json=ecdsaAttestationKey,proto3" json:"ecdsa_attestation_key,omitempty"` // should be 64 bytes // The certification data. CertificationData *CertificationData `protobuf:"bytes,3,opt,name=certification_data,json=certificationData,proto3" json:"certification_data,omitempty"` } func (x *Ecdsa256BitQuoteV4AuthData) Reset() { *x = Ecdsa256BitQuoteV4AuthData{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Ecdsa256BitQuoteV4AuthData) String() string { return protoimpl.X.MessageStringOf(x) } func (*Ecdsa256BitQuoteV4AuthData) ProtoMessage() {} func (x *Ecdsa256BitQuoteV4AuthData) ProtoReflect() protoreflect.Message { mi := &file_tdx_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 Ecdsa256BitQuoteV4AuthData.ProtoReflect.Descriptor instead. func (*Ecdsa256BitQuoteV4AuthData) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{3} } func (x *Ecdsa256BitQuoteV4AuthData) GetSignature() []byte { if x != nil { return x.Signature } return nil } func (x *Ecdsa256BitQuoteV4AuthData) GetEcdsaAttestationKey() []byte { if x != nil { return x.EcdsaAttestationKey } return nil } func (x *Ecdsa256BitQuoteV4AuthData) GetCertificationData() *CertificationData { if x != nil { return x.CertificationData } return nil } type CertificationData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Supported values: // // - 1 (PCK identifier: PPID in plain text, CPUSVN and PCESVN) // - 2 (PCK identifier: PPID encrypted using RSA-2048-OAEP, CPUSVN and PCESVN) // - 3 (PCK identifier: PPID encrypted using RSA-3072-OAEP, CPUSVN and PCESVN) // - 4 (PCK Leaf Certificate in plain text, currently not supported) // - 5 (Concatenated PCK Cert Chain) // - 6 (QE Report Certification Data) // - 7 (PLATFORM_MANIFEST, currently not supported) CertificateDataType uint32 `protobuf:"varint,1,opt,name=certificate_data_type,json=certificateDataType,proto3" json:"certificate_data_type,omitempty"` // Size of Certification Data field Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` QeReportCertificationData *QEReportCertificationData `protobuf:"bytes,3,opt,name=qe_report_certification_data,json=qeReportCertificationData,proto3" json:"qe_report_certification_data,omitempty"` } func (x *CertificationData) Reset() { *x = CertificationData{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CertificationData) String() string { return protoimpl.X.MessageStringOf(x) } func (*CertificationData) ProtoMessage() {} func (x *CertificationData) ProtoReflect() protoreflect.Message { mi := &file_tdx_proto_msgTypes[4] 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 CertificationData.ProtoReflect.Descriptor instead. func (*CertificationData) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{4} } func (x *CertificationData) GetCertificateDataType() uint32 { if x != nil { return x.CertificateDataType } return 0 } func (x *CertificationData) GetSize() uint32 { if x != nil { return x.Size } return 0 } func (x *CertificationData) GetQeReportCertificationData() *QEReportCertificationData { if x != nil { return x.QeReportCertificationData } return nil } type QEReportCertificationData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields QeReport *EnclaveReport `protobuf:"bytes,1,opt,name=qe_report,json=qeReport,proto3" json:"qe_report,omitempty"` QeReportSignature []byte `protobuf:"bytes,2,opt,name=qe_report_signature,json=qeReportSignature,proto3" json:"qe_report_signature,omitempty"` // should be 64 bytes QeAuthData *QeAuthData `protobuf:"bytes,3,opt,name=qe_auth_data,json=qeAuthData,proto3" json:"qe_auth_data,omitempty"` PckCertificateChainData *PCKCertificateChainData `protobuf:"bytes,4,opt,name=pck_certificate_chain_data,json=pckCertificateChainData,proto3" json:"pck_certificate_chain_data,omitempty"` } func (x *QEReportCertificationData) Reset() { *x = QEReportCertificationData{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QEReportCertificationData) String() string { return protoimpl.X.MessageStringOf(x) } func (*QEReportCertificationData) ProtoMessage() {} func (x *QEReportCertificationData) ProtoReflect() protoreflect.Message { mi := &file_tdx_proto_msgTypes[5] 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 QEReportCertificationData.ProtoReflect.Descriptor instead. func (*QEReportCertificationData) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{5} } func (x *QEReportCertificationData) GetQeReport() *EnclaveReport { if x != nil { return x.QeReport } return nil } func (x *QEReportCertificationData) GetQeReportSignature() []byte { if x != nil { return x.QeReportSignature } return nil } func (x *QEReportCertificationData) GetQeAuthData() *QeAuthData { if x != nil { return x.QeAuthData } return nil } func (x *QEReportCertificationData) GetPckCertificateChainData() *PCKCertificateChainData { if x != nil { return x.PckCertificateChainData } return nil } type PCKCertificateChainData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Supported values: // // - 1 (PCK identifier: PPID in plain text, CPUSVN and PCESVN) // - 2 (PCK identifier: PPID encrypted using RSA-2048-OAEP, CPUSVN and PCESVN) // - 3 (PCK identifier: PPID encrypted using RSA-3072-OAEP, CPUSVN and PCESVN) // - 4 (PCK Leaf Certificate in plain text, currently not supported) // - 5 (Concatenated PCK Cert Chain) // - 6 (QE Report Certification Data) // - 7 (PLATFORM_MANIFEST, currently not supported) CertificateDataType uint32 `protobuf:"varint,1,opt,name=certificate_data_type,json=certificateDataType,proto3" json:"certificate_data_type,omitempty"` // Size of Certification Data field Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` PckCertChain []byte `protobuf:"bytes,3,opt,name=pck_cert_chain,json=pckCertChain,proto3" json:"pck_cert_chain,omitempty"` } func (x *PCKCertificateChainData) Reset() { *x = PCKCertificateChainData{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PCKCertificateChainData) String() string { return protoimpl.X.MessageStringOf(x) } func (*PCKCertificateChainData) ProtoMessage() {} func (x *PCKCertificateChainData) ProtoReflect() protoreflect.Message { mi := &file_tdx_proto_msgTypes[6] 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 PCKCertificateChainData.ProtoReflect.Descriptor instead. func (*PCKCertificateChainData) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{6} } func (x *PCKCertificateChainData) GetCertificateDataType() uint32 { if x != nil { return x.CertificateDataType } return 0 } func (x *PCKCertificateChainData) GetSize() uint32 { if x != nil { return x.Size } return 0 } func (x *PCKCertificateChainData) GetPckCertChain() []byte { if x != nil { return x.PckCertChain } return nil } type QeAuthData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The parsed data size. ParsedDataSize uint32 `protobuf:"varint,1,opt,name=parsed_data_size,json=parsedDataSize,proto3" json:"parsed_data_size,omitempty"` // should be 2 bytes // The data. Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` } func (x *QeAuthData) Reset() { *x = QeAuthData{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QeAuthData) String() string { return protoimpl.X.MessageStringOf(x) } func (*QeAuthData) ProtoMessage() {} func (x *QeAuthData) ProtoReflect() protoreflect.Message { mi := &file_tdx_proto_msgTypes[7] 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 QeAuthData.ProtoReflect.Descriptor instead. func (*QeAuthData) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{7} } func (x *QeAuthData) GetParsedDataSize() uint32 { if x != nil { return x.ParsedDataSize } return 0 } func (x *QeAuthData) GetData() []byte { if x != nil { return x.Data } return nil } type EnclaveReport struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CpuSvn []byte `protobuf:"bytes,1,opt,name=cpu_svn,json=cpuSvn,proto3" json:"cpu_svn,omitempty"` // should be 16 bytes MiscSelect uint32 `protobuf:"varint,2,opt,name=misc_select,json=miscSelect,proto3" json:"misc_select,omitempty"` // should be 4 bytes Reserved1 []byte `protobuf:"bytes,3,opt,name=reserved1,proto3" json:"reserved1,omitempty"` // should be 28 bytes Attributes []byte `protobuf:"bytes,4,opt,name=attributes,proto3" json:"attributes,omitempty"` // should be 16 bytes MrEnclave []byte `protobuf:"bytes,5,opt,name=mr_enclave,json=mrEnclave,proto3" json:"mr_enclave,omitempty"` // should be 32 bytes Reserved2 []byte `protobuf:"bytes,6,opt,name=reserved2,proto3" json:"reserved2,omitempty"` // should be 32 bytes MrSigner []byte `protobuf:"bytes,7,opt,name=mr_signer,json=mrSigner,proto3" json:"mr_signer,omitempty"` // should be 32 bytes Reserved3 []byte `protobuf:"bytes,8,opt,name=reserved3,proto3" json:"reserved3,omitempty"` // should be 96 bytes IsvProdId uint32 `protobuf:"varint,9,opt,name=isv_prod_id,json=isvProdId,proto3" json:"isv_prod_id,omitempty"` // should be 2 bytes IsvSvn uint32 `protobuf:"varint,10,opt,name=isv_svn,json=isvSvn,proto3" json:"isv_svn,omitempty"` // should be 2 bytes Reserved4 []byte `protobuf:"bytes,11,opt,name=reserved4,proto3" json:"reserved4,omitempty"` // should be 60 bytes ReportData []byte `protobuf:"bytes,12,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // should be 64 bytes } func (x *EnclaveReport) Reset() { *x = EnclaveReport{} if protoimpl.UnsafeEnabled { mi := &file_tdx_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EnclaveReport) String() string { return protoimpl.X.MessageStringOf(x) } func (*EnclaveReport) ProtoMessage() {} func (x *EnclaveReport) ProtoReflect() protoreflect.Message { mi := &file_tdx_proto_msgTypes[8] 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 EnclaveReport.ProtoReflect.Descriptor instead. func (*EnclaveReport) Descriptor() ([]byte, []int) { return file_tdx_proto_rawDescGZIP(), []int{8} } func (x *EnclaveReport) GetCpuSvn() []byte { if x != nil { return x.CpuSvn } return nil } func (x *EnclaveReport) GetMiscSelect() uint32 { if x != nil { return x.MiscSelect } return 0 } func (x *EnclaveReport) GetReserved1() []byte { if x != nil { return x.Reserved1 } return nil } func (x *EnclaveReport) GetAttributes() []byte { if x != nil { return x.Attributes } return nil } func (x *EnclaveReport) GetMrEnclave() []byte { if x != nil { return x.MrEnclave } return nil } func (x *EnclaveReport) GetReserved2() []byte { if x != nil { return x.Reserved2 } return nil } func (x *EnclaveReport) GetMrSigner() []byte { if x != nil { return x.MrSigner } return nil } func (x *EnclaveReport) GetReserved3() []byte { if x != nil { return x.Reserved3 } return nil } func (x *EnclaveReport) GetIsvProdId() uint32 { if x != nil { return x.IsvProdId } return 0 } func (x *EnclaveReport) GetIsvSvn() uint32 { if x != nil { return x.IsvSvn } return 0 } func (x *EnclaveReport) GetReserved4() []byte { if x != nil { return x.Reserved4 } return nil } func (x *EnclaveReport) GetReportData() []byte { if x != nil { return x.ReportData } return nil } var File_tdx_proto protoreflect.FileDescriptor var file_tdx_proto_rawDesc = []byte{ 0x0a, 0x09, 0x74, 0x64, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x74, 0x64, 0x78, 0x22, 0xf1, 0x01, 0x0a, 0x07, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x56, 0x34, 0x12, 0x23, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x74, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x54, 0x44, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x0b, 0x74, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x45, 0x63, 0x64, 0x73, 0x61, 0x32, 0x35, 0x36, 0x42, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x56, 0x34, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0xde, 0x01, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 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, 0x30, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x65, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x65, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x71, 0x65, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x71, 0x65, 0x53, 0x76, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x63, 0x65, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x63, 0x65, 0x53, 0x76, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x71, 0x65, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x71, 0x65, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x22, 0xff, 0x02, 0x0a, 0x0b, 0x54, 0x44, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x65, 0x65, 0x5f, 0x74, 0x63, 0x62, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x65, 0x65, 0x54, 0x63, 0x62, 0x53, 0x76, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x72, 0x5f, 0x73, 0x65, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6d, 0x72, 0x53, 0x65, 0x61, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x61, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6d, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x65, 0x61, 0x6d, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x74, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x66, 0x61, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x78, 0x66, 0x61, 0x6d, 0x12, 0x13, 0x0a, 0x05, 0x6d, 0x72, 0x5f, 0x74, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6d, 0x72, 0x54, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x72, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x72, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x72, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x72, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x74, 0x6d, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x74, 0x6d, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb5, 0x01, 0x0a, 0x1a, 0x45, 0x63, 0x64, 0x73, 0x61, 0x32, 0x35, 0x36, 0x42, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x56, 0x34, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x63, 0x64, 0x73, 0x61, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x65, 0x63, 0x64, 0x73, 0x61, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x22, 0xbc, 0x01, 0x0a, 0x11, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x5f, 0x0a, 0x1c, 0x71, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x51, 0x45, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x19, 0x71, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x8a, 0x02, 0x0a, 0x19, 0x51, 0x45, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2f, 0x0a, 0x09, 0x71, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x45, 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x08, 0x71, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x71, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x71, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x31, 0x0a, 0x0c, 0x71, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x51, 0x65, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x71, 0x65, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x12, 0x59, 0x0a, 0x1a, 0x70, 0x63, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x64, 0x78, 0x2e, 0x50, 0x43, 0x4b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x17, 0x70, 0x63, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x87, 0x01, 0x0a, 0x17, 0x50, 0x43, 0x4b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x63, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x63, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x4a, 0x0a, 0x0a, 0x51, 0x65, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf7, 0x02, 0x0a, 0x0d, 0x45, 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x70, 0x75, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x70, 0x75, 0x53, 0x76, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x73, 0x63, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x69, 0x73, 0x63, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x31, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x31, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x72, 0x5f, 0x65, 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6d, 0x72, 0x45, 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x32, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x32, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x33, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x33, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x73, 0x76, 0x5f, 0x70, 0x72, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x69, 0x73, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x76, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x73, 0x76, 0x53, 0x76, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x34, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x34, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x74, 0x64, 0x78, 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x64, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_tdx_proto_rawDescOnce sync.Once file_tdx_proto_rawDescData = file_tdx_proto_rawDesc ) func file_tdx_proto_rawDescGZIP() []byte { file_tdx_proto_rawDescOnce.Do(func() { file_tdx_proto_rawDescData = protoimpl.X.CompressGZIP(file_tdx_proto_rawDescData) }) return file_tdx_proto_rawDescData } var file_tdx_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_tdx_proto_goTypes = []interface{}{ (*QuoteV4)(nil), // 0: tdx.QuoteV4 (*Header)(nil), // 1: tdx.Header (*TDQuoteBody)(nil), // 2: tdx.TDQuoteBody (*Ecdsa256BitQuoteV4AuthData)(nil), // 3: tdx.Ecdsa256BitQuoteV4AuthData (*CertificationData)(nil), // 4: tdx.CertificationData (*QEReportCertificationData)(nil), // 5: tdx.QEReportCertificationData (*PCKCertificateChainData)(nil), // 6: tdx.PCKCertificateChainData (*QeAuthData)(nil), // 7: tdx.QeAuthData (*EnclaveReport)(nil), // 8: tdx.EnclaveReport } var file_tdx_proto_depIdxs = []int32{ 1, // 0: tdx.QuoteV4.header:type_name -> tdx.Header 2, // 1: tdx.QuoteV4.td_quote_body:type_name -> tdx.TDQuoteBody 3, // 2: tdx.QuoteV4.signed_data:type_name -> tdx.Ecdsa256BitQuoteV4AuthData 4, // 3: tdx.Ecdsa256BitQuoteV4AuthData.certification_data:type_name -> tdx.CertificationData 5, // 4: tdx.CertificationData.qe_report_certification_data:type_name -> tdx.QEReportCertificationData 8, // 5: tdx.QEReportCertificationData.qe_report:type_name -> tdx.EnclaveReport 7, // 6: tdx.QEReportCertificationData.qe_auth_data:type_name -> tdx.QeAuthData 6, // 7: tdx.QEReportCertificationData.pck_certificate_chain_data:type_name -> tdx.PCKCertificateChainData 8, // [8:8] is the sub-list for method output_type 8, // [8:8] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name } func init() { file_tdx_proto_init() } func file_tdx_proto_init() { if File_tdx_proto != nil { return } if !protoimpl.UnsafeEnabled { file_tdx_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QuoteV4); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Header); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TDQuoteBody); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Ecdsa256BitQuoteV4AuthData); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CertificationData); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QEReportCertificationData); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PCKCertificateChainData); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QeAuthData); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_tdx_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EnclaveReport); 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_tdx_proto_rawDesc, NumEnums: 0, NumMessages: 9, NumExtensions: 0, NumServices: 0, }, GoTypes: file_tdx_proto_goTypes, DependencyIndexes: file_tdx_proto_depIdxs, MessageInfos: file_tdx_proto_msgTypes, }.Build() File_tdx_proto = out.File file_tdx_proto_rawDesc = nil file_tdx_proto_goTypes = nil file_tdx_proto_depIdxs = nil } go-tdx-guest-0.3.1/testing/000077500000000000000000000000001456371376300155365ustar00rootroot00000000000000go-tdx-guest-0.3.1/testing/client/000077500000000000000000000000001456371376300170145ustar00rootroot00000000000000go-tdx-guest-0.3.1/testing/client/client.go000066400000000000000000000033461456371376300206270ustar00rootroot00000000000000// 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 client (in testing) allows tests to get a fake or real tdx-guest device. package client import ( "testing" "github.com/google/go-tdx-guest/client" test "github.com/google/go-tdx-guest/testing" ) // GetTdxGuest is a testing helper function that retrieves the // appropriate TDX-guest device from the flags passed into "go test". // // If using a test guest device, this will also produce a fake Device. func GetTdxGuest(tcs []test.TestCase, tb testing.TB) client.Device { tb.Helper() if client.UseDefaultTdxGuestDevice() { tdxTestDevice, err := test.TcDevice(tcs) if err != nil { tb.Fatalf("failed to create test device: %v", err) } return tdxTestDevice } client, err := client.OpenDevice() if err != nil { tb.Fatalf("Failed to open TDX guest device: %v", err) } return client } // GetMockTdxQuoteProvider is a testing helper function that produces a fake TDX quote provider. func GetMockTdxQuoteProvider(tcs []test.TestCase, tb testing.TB) client.QuoteProvider { tb.Helper() tdxTestQuoteProvider, err := test.TcQuoteProvider(tcs) if err != nil { tb.Fatalf("Failed to create test quote provider: %v", err) } return tdxTestQuoteProvider } go-tdx-guest-0.3.1/testing/mocks.go000066400000000000000000000107761456371376300172140ustar00rootroot00000000000000// 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 defines the mock tdx-guest device package testing import ( "fmt" "strings" labi "github.com/google/go-tdx-guest/client/linuxabi" ) // GetReportResponse represents a mocked response to a command request. type GetReportResponse struct { Resp labi.TdxReportReq EsResult labi.EsResult } // GetQuoteResponse represents a mocked response to a command request. type GetQuoteResponse struct { Resp labi.TdxQuoteHdr EsResult labi.EsResult } // Device represents a fake tdx-guest device with pre-programmed responses. type Device struct { isOpen bool reportResponse map[[labi.TdReportDataSize]byte]any quoteResponse map[[labi.TdReportSize]byte]any } // Match returns true if both errors match expectations closely enough func Match(got error, want string) bool { if got == nil { return want == "" } return strings.Contains(got.Error(), want) } // Open changes the mock device's state to open. func (d *Device) Open(_ string) error { if d.isOpen { return fmt.Errorf("device is already open") } d.isOpen = true return nil } // Close changes the mock device's state to close. func (d *Device) Close() error { if !d.isOpen { return fmt.Errorf("device is already closed") } d.isOpen = false return nil } func (d *Device) getReport(req *labi.TdxReportReq) (uintptr, error) { tdReportRespI, ok := d.reportResponse[req.ReportData] if !ok { return 0, fmt.Errorf("test error: no response for %v", req.ReportData) } tdReportResp, ok := tdReportRespI.(*GetReportResponse) if !ok { return 0, fmt.Errorf("test error: incorrect response for %v", tdReportRespI) } esResult := uintptr(tdReportResp.EsResult) req.TdReport = tdReportResp.Resp.TdReport return esResult, nil } func (d *Device) getQuote(req *labi.TdxQuoteHdr) (uintptr, error) { var report [labi.TdReportSize]byte copy(report[:], req.Data[:labi.TdReportSize]) quoteRespI, ok := d.quoteResponse[report] if !ok { return 0, fmt.Errorf("test error: no response for %v", report) } quoteResp, ok := quoteRespI.(*GetQuoteResponse) if !ok { return 0, fmt.Errorf("test error: incorrect response for %v", quoteRespI) } esResult := uintptr(quoteResp.EsResult) copy(req.Data[:], quoteResp.Resp.Data[:]) req.OutLen = quoteResp.Resp.OutLen return esResult, 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.TdxQuoteReq: switch command { case labi.IocTdxGetQuote: return d.getQuote(sreq.Buffer.(*labi.TdxQuoteHdr)) default: return 0, fmt.Errorf("unexpected request value: %v", req) } case *labi.TdxReportReq: switch command { case labi.IocTdxGetReport: return d.getReport(sreq) default: return 0, fmt.Errorf("unexpected request value: %v", req) } } return 0, fmt.Errorf("unexpected request value: %v", req) } // HTTPResponse represents structure for containing header and body type HTTPResponse struct { Header map[string][]string Body []byte } // Getter represents a static server for request/respond url -> body contents. type Getter struct { Responses map[string]HTTPResponse } // Get returns a registered response for a given URL. func (g *Getter) Get(url string) (map[string][]string, []byte, error) { v, ok := g.Responses[url] if !ok { return nil, nil, fmt.Errorf("404: %s", url) } return v.Header, v.Body, nil } // TdxQuoteProvider represents a fake quote provider with pre-programmed responses. type TdxQuoteProvider struct { isSupported bool rawQuoteResponse map[[labi.TdReportDataSize]byte][]uint8 } // IsSupported checks if mock quote provider is supported. func (p *TdxQuoteProvider) IsSupported() error { if p.isSupported { return nil } return fmt.Errorf("configfs not supported") } // GetRawQuote returns pre-programmed response as raw quote. func (p *TdxQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { return p.rawQuoteResponse[reportData], nil } go-tdx-guest-0.3.1/testing/test_cases.go000066400000000000000000000235601456371376300202300ustar00rootroot00000000000000// 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 defines the mock tdx-guest device package testing import ( labi "github.com/google/go-tdx-guest/client/linuxabi" "github.com/google/go-tdx-guest/testing/testdata" ) var pckCrlIssuerChain = []string{ "-----BEGIN%20CERTIFICATE-----%0AMIICljCCAj2gAwIBAgIVAJVvXc29G%2BHpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC%0AMGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD%0Ab3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw%0ACQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg%0ABgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs%0AIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex%0ACzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB%2F7t21lXSO%0A2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z%2BUiRZCnqR7psOvgqFeSxlmTlJl%0AeTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBS%0ABgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy%0AdmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d%0Azb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYB%0AAf8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w%2Bi6VYGW3UF%2F22uaXe0YJDj1Ue%0AnA%2BTjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN%2B%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG%0AA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0%0AaW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT%0AAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj%2FiPWsCzaEKi7%0A1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB%0AuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ%0AMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50%0AZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV%0AUr9QGzknBqwwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYBAf8CAQEwCgYI%0AKoZIzj0EAwIDSQAwRgIhAOW%2F5QkR%2BS9CiSDcNoowLuPRLsWGf%2FYi7GSX94BgwTwg%0AAiEA4J0lrHoMs%2BXo5o%2FsX6O9QWxHRAvZUGOdRQ7cvqRXaqI%3D%0A-----END%20CERTIFICATE-----%0A"} var tcbInfoIssuerChain = []string{ "-----BEGIN%20CERTIFICATE-----%0AMIICizCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNTAxMFoXDTI1MDUyMTEwNTAxMFowbDEeMBwG%0AA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw%0Ab3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD%0AVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv%0AP%2BmAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh%2FzN3C4xvpoouGlirMba%2BW2lju%0AypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f%0ABEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz%0ALmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK%0AQEmORYQD6RSRvfRVMA4GA1UdDwEB%2FwQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG%0ASM49BAMCA0cAMEQCIB9C8wOAN%2FImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj%0AftbrNGsGU8YH211dRiYNoPPu19Zp%2Fze8JmhujB0oBw%3D%3D%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG%0AA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0%0AaW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT%0AAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj%2FiPWsCzaEKi7%0A1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB%0AuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ%0AMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50%0AZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV%0AUr9QGzknBqwwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYBAf8CAQEwCgYI%0AKoZIzj0EAwIDSQAwRgIhAOW%2F5QkR%2BS9CiSDcNoowLuPRLsWGf%2FYi7GSX94BgwTwg%0AAiEA4J0lrHoMs%2BXo5o%2FsX6O9QWxHRAvZUGOdRQ7cvqRXaqI%3D%0A-----END%20CERTIFICATE-----%0A", } var qeIdentityIssuerChain = []string{ "-----BEGIN%20CERTIFICATE-----%0AMIICizCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNTAxMFoXDTI1MDUyMTEwNTAxMFowbDEeMBwG%0AA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw%0Ab3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD%0AVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv%0AP%2BmAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh%2FzN3C4xvpoouGlirMba%2BW2lju%0AypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f%0ABEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz%0ALmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK%0AQEmORYQD6RSRvfRVMA4GA1UdDwEB%2FwQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG%0ASM49BAMCA0cAMEQCIB9C8wOAN%2FImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj%0AftbrNGsGU8YH211dRiYNoPPu19Zp%2Fze8JmhujB0oBw%3D%3D%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG%0AA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0%0AaW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT%0AAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj%2FiPWsCzaEKi7%0A1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB%0AuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ%0AMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50%0AZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV%0AUr9QGzknBqwwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYBAf8CAQEwCgYI%0AKoZIzj0EAwIDSQAwRgIhAOW%2F5QkR%2BS9CiSDcNoowLuPRLsWGf%2FYi7GSX94BgwTwg%0AAiEA4J0lrHoMs%2BXo5o%2FsX6O9QWxHRAvZUGOdRQ7cvqRXaqI%3D%0A-----END%20CERTIFICATE-----%0A", } // PckCrlHeader is the response header for pck crl var PckCrlHeader = map[string][]string{ "Sgx-Pck-Crl-Issuer-Chain": pckCrlIssuerChain, } // TcbInfoHeader is the response header for pck crl var TcbInfoHeader = map[string][]string{ "Tcb-Info-Issuer-Chain": tcbInfoIssuerChain, } // QeIdentityHeader is the response header for pck crl var QeIdentityHeader = map[string][]string{ "Sgx-Enclave-Identity-Issuer-Chain": qeIdentityIssuerChain, } // TestGetter is a local getter tied to the included sample quote var TestGetter = &Getter{ Responses: map[string]HTTPResponse{ "https://api.trustedservices.intel.com/tdx/certification/v4/qe/identity": { Header: QeIdentityHeader, Body: testdata.QeIdentityBody, }, "https://api.trustedservices.intel.com/tdx/certification/v4/tcb?fmspc=50806f000000": { Header: TcbInfoHeader, Body: testdata.TcbInfoBody, }, "https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=platform&encoding=der": { Header: PckCrlHeader, Body: testdata.PckCrlBody, }, "https://certificates.trustedservices.intel.com/IntelSGXRootCA.der": { Header: nil, Body: testdata.RootCrlBody, }, }, } // reportdata defines a ReportData example that is all zeros except the last byte is 1. var reportdata = [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} // TestCase represents a get_quote input/output test case. type TestCase struct { Name string Input [labi.TdReportDataSize]uint8 Report [labi.TdReportSize]uint8 Quote []uint8 EsResult labi.EsResult WantErr string } // TestCases returns common test cases for get_report. func TestCases() []TestCase { var report [1024]byte copy(report[:], testdata.RawReport) return []TestCase{ { Name: "zeros", Input: reportdata, Report: report, Quote: testdata.RawQuote, }, } } // TcQuoteProvider returns a mock quote provider populated from test cases inputs and expected outputs. func TcQuoteProvider(tcs []TestCase) (*TdxQuoteProvider, error) { rawQuoteResponses := map[[labi.TdReportDataSize]byte][]uint8{} for _, tc := range tcs { rawQuoteResponses[tc.Input] = tc.Quote } return &TdxQuoteProvider{ isSupported: true, rawQuoteResponse: rawQuoteResponses, }, nil } // TcDevice returns a mock device populated from test cases inputs and expected outputs. func TcDevice(tcs []TestCase) (*Device, error) { reportResponses := map[[labi.TdReportDataSize]byte]any{} quoteResponses := map[[labi.TdReportSize]byte]any{} for _, tc := range tcs { reportResponses[tc.Input] = &GetReportResponse{ Resp: labi.TdxReportReq{ TdReport: tc.Report, }, EsResult: tc.EsResult, } var idQuote [labi.ReqBufSize]byte copy(idQuote[:], tc.Quote) quoteResponses[tc.Report] = &GetQuoteResponse{ Resp: labi.TdxQuoteHdr{ Status: labi.GetQuoteSuccess, OutLen: uint32(len(tc.Quote)), Data: idQuote, }, EsResult: tc.EsResult, } } return &Device{ reportResponse: reportResponses, quoteResponse: quoteResponses, }, nil } go-tdx-guest-0.3.1/testing/testdata/000077500000000000000000000000001456371376300173475ustar00rootroot00000000000000go-tdx-guest-0.3.1/testing/testdata/README.md000066400000000000000000000117751456371376300206410ustar00rootroot00000000000000# `testdata` This folder contains embedded files that serve as sample API responses, intended for testing purposes. These responses can be used to stimulate the behavior of Intel PCS APIs without actually making network access. This folder also contains a sample TDX quote version 4 which is only used for testing purpose. ## Files ### `pckcrl` This file serves as sample for Intel PCS API response for PCK certificate Revocation List. This response is specifically designed to check whether a PCK certificate is revoked or not. This sample API follows a structure similar to `https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=platform&encoding=der` ### `sample_tcbInfo_response` This file serves as sample for Intel PCS API response to retrieve TDX TCB information for given FMSPC. This response helps in determining the status of a TDX TCB level for a given platform needs to be done using TDX TCB information according to the following algorithm: 1. Retrieve FMSPC value from SGX PCK Certificate assigned to a given platform. 2. Retrieve TDX TCB Info matching the FMSPC value. 3. Go over the sorted collection of TCB Levels retrieved from TCB Info starting from the first item on the list: a. Compare all of the SGX TCB Comp SVNs retrieved from the SGX PCK Certificate (from 01 to 16) with the corresponding values of SVNs in sgxtcbcomponents array of TCB Level. If all SGX TCB Comp SVNs in the certificate are greater or equal to the corresponding values in TCB Level, go to 3.b, otherwise move to the next item on TCB Levels list. b. Compare PCESVN value retrieved from the SGX PCK certificate with the corresponding value in the TCB Level. If it is greater or equal to the value in TCB Level, go to 3.c, otherwise move to the next item on TCB Levels list. c. Compare all of the SVNs in TEE TCB SVN array retrieved from TD Report in Quote (from index 0 to 15) with the corresponding values of SVNs in tdxtcbcomponents array of TCB Level. If all TEE TCB SVNs in the TD Report are greater or equal to the corresponding values in TCB Level, read status assigned to this TCB level. Otherwise, move to the next item on TCB Levels list. 4. For the selected TCB level verify that SVN at index 1 in tdxtcbcomponents array matches the value of SVN at index 1 in TEE TCB SVNs array (from TD Report in Quote). In case of a mismatch the selected TCB level should be rejected as TCB Info that was used for the comparison is not supported for this platform configuration. 5. If no TCB level matches the SGX PCK Certificate and TD Report, then the TCB level is not supported. This sample API follows a structure similar to `https://api.trustedservices.intel.com/tdx/certification/v4/tcb?fmspc=50806f000000` ### `sample_qeIdentity_response` This file serves as sample for Intel PCS API response to retrieve QE identity response. This response helps in determining if the identity of a SGX Enclave (represented by SGX Enclave Report) matches a valid, up-to-date Enclave Identity issued by Intel requires following steps: 1. Retrieve Enclave Identity(TDX QE) from PCS and verify that it is a valid structure issued by Intel. 2. Perform the following comparison of SGX Enclave Report against the retrieved Enclave Identity: a. Verify if MRSIGNER field retrieved from SGX Enclave Report is equal to the value of mrsigner field in Enclave Identity. b. Verify if ISVPRODID field retrieved from SGX Enclave Report is equal to the value of isvprodid field in Enclave Identity. c. Apply miscselectMask (binary mask) from Enclave Identity to MISCSELECT field retrieved from SGX Enclave Report. Verify if the outcome (miscselectMask & MISCSELECT) is equal to the value of miscselect field in Enclave Identity. d. Apply attributesMask (binary mask) from Enclave Identity to ATTRIBUTES field retrieved from SGX Enclave Report. Verify if the outcome (attributesMask & ATTRIBUTES) is equal to the value of attributes field in Enclave Identity. 3. If any of the checks above fail, the identity of the enclave does not match Enclave Identity published by Intel. 4. Determine a TCB status of the Enclave: a. Retrieve a collection of TCB Levels (sorted by ISVSVNs) from tcbLevels field in Enclave Identity structure. b. Go over the list of TCB Levels (descending order) and find the one that has ISVSVN that is lower or equal to the ISVSVN value from SGX Enclave Report. c. If a TCB level is found, read its status from tcbStatus field, otherwise the TCB Level is not supported. This sample API follows a structure similar to `https://api.trustedservices.intel.com/tdx/certification/v4/qe/identity` ### `rootcrl.der` This file serves as sample for Intel PCS API response for Root certificate Revocation List. This response is specifically designed to check whether a Root certificate is revoked or not. This sample API follows a structure similar to `https://certificates.trustedservices.intel.com/IntelSGXRootCA.der` go-tdx-guest-0.3.1/testing/testdata/pckcrl000066400000000000000000000051471456371376300205570ustar00rootroot000000000000000‚ c0‚ 0 *†HÎ=0p1"0 U Intel SGX PCK Platform CA10U Intel Corporation10U Santa Clara1 0 U CA1 0 UUS 230608072752Z 230708072752Z0‚ 403oÃNP#ç(’45Ö¤¸§A­Í 230608072752Z0 0 U 04Šù$NZýÝsÃÖ:õèµs~V 230608072752Z0 0 U 04±%yxÏ©ÌÝY«øÅÊrúã§Š› 230608072752Z0 0 U 03tþ¦©r¾(Cò˜5Ørù³ 230608072752Z0 0 U 04ùÄïV³«HÕwáºíô¿ˆB¹ 230608072752Z0 0 U 03àwž_Äò‡0Ö°|š0æ³  230608072752Z0 0 U 04ÍâBO—,ê”ÿ#™7ôØ %Ö  230608072752Z0 0 U 03l3å›dP}<ñ,àIïRs 230608072752Z0 0 U 04ß×V¶jt—ô;[µŠÚÓô÷©7 230608072752Z0 0 U 03(¯H[lö~@š9ÕËZîE˜÷¨ú{ 230608072752Z0 0 U 04û‹-®À’ÊÚŠ©¼Oòñ FfŒ 230608072752Z0 0 U 04ÍHP¬R½Ìi¦¦ðXȼW»Ðµød 230608072752Z0 0 U 04™MÓfoRuû€_•ݽPË&yØ­ 230608072752Z0 0 U 03i%"tÙ^íõEtbúÐïL 230608072752Z0 0 U 03aò¿sã›Nª'ؽsÒCµ¿€ 230608072752Z0 0 U 039’¾…–.ÿ8•žl.ÿQ¤µ 230608072752Z0 0 U 03cŸšP@ýÏñ‘è¤ûð†í`9q 230608072752Z0 0 U 04•S?’IÜQ5DÍÈ0¿·ñó 230608072752Z0 0 U 03ÚC  hêy·ÂÞ®¬ I‹ß²¯ 230608072752Z0 0 U 04gu;äp®§cûìLEI¼Û™3 230608072752Z0 0 U 034¿»zœVGá¶÷·nÓïhß 230608072752Z0 0 U 04…ÓÉ8w§àMœžZÖtŸóÿ«‡ 230608072752Z0 0 U 04“ˆ|¤Az’;ÑþÒ›)Iòµ´ 230608072752Z0 0 U 03$˜Übƒ“ –ý‹ò:7¬¾&£¾ÔW 230608072752Z0 0 U 04Šfñ§IH†ghœÃ:ÅLf+q.s 230608072752Z0 0 U 04¯Á6½Ól·˜]d¨€Ó Ú 230608072752Z0 0 U 04ïàK,3Ð6ªÉl¦s¿šG¶M\» 230608072752Z0 0 U 04ƒÙ¬‹µ ÑÆÈ ­q.„0Už×ó 230608072752Z0 0 U 03y1ýPµ¿Å·¶ÞØ´[‹…) 230608072752Z0 0 U 03¢)p½åÕ{߃9HN# 230608072752Z0 0 U 03‡²Ã³-#äÎóA—¹ZðÈ­õ 230608072752Z0 0 U 04šý.é G5P¡gÙ–‘7ÇP-  230608072752Z0 0 U 03D°ñ(¡;im>©Çp ±^ÅÚ 230608072752Z0 0 U 04§…ŸW˜.ðæ}7¼ŽòïZÈ5ÿ© 230608072752Z0 0 U 03zãwH©ùôÆ;§«Å“Î 230608072752Z0 0 U 03ˆK3&™8Á•ªÊuÚu8ß  230608072752Z0 0 U 03,<Æþ’yÛÕÎ9ò¨˜Í¥¡uá 230608072752Z0 0 U 03qyHhu #Kéyä·Üæóïd¶Œ 230608072752Z0 0 U 04vï,9Á6èe‹ns–±×DZ'c 230608072752Z0 0 U 04Ãà%ü©•óoY´„g“ž>4æ6o 230608072752Z0 0 U 04Œ_k2WÚ±t)âæ©eÖs0`j 230608072752Z0 0 U 04¡|Qr.ÁàÃ'è½ðRœ¾ÄæH 230608072752Z0 0 U  /0-0 U0U#0€•o]ͽáé@IÉÔô3ÎW ÞT0 *†HÎ=I0F!ÇÐÂØ¥Ûš#* ªè£™ÎØ»c¢*V·™e1ëÚ«!ê÷Ø,4˜U;Ø÷ˆÇº.q%š¢®ËYÛ帠q–Úgo-tdx-guest-0.3.1/testing/testdata/report.dat000066400000000000000000000020001456371376300213440ustar00rootroot00000000000000× ÑUÒMn¥ ¨.«…¬ÝV‚'°‹#'6üˆ=^œe«-eXq`ÄÿÒ5"‹žTóÄNézÇ ÷Fw¸G 5ñÉÔ'°ul“¥‹ð~½ý@ð‹~½ýÔ¥êZrì¶P·šMNc[CKàÉÓI Os÷åÐÑÈnÿ/ÒyÁad©=Õ¿7=ƒC(Ô`¶“¯ž»†[²ÎÓ É¨›Hi©ú¶¾ ZScÆVç1vlÕHøcR+‰Cï°µêâ«IŽPAf£ÙÊ|M΢R %ÓZeܦGrÝÊMÊFgo-tdx-guest-0.3.1/testing/testdata/rootcrl.der000066400000000000000000000004451456371376300215320ustar00rootroot000000000000000‚!0È0 *†HÎ=0h10U Intel SGX Root CA10U Intel Corporation10U Santa Clara1 0 U CA1 0 UUS 230403102251Z 240402102251Z /0-0 U0U#0€"e ÖZ4‰óƒ´•R¿P9'¬0 *†HÎ=H0E QW}GÙû¡W¶_µôe{¼^V̯sZñ¹c×€Z±!“ì6çêú_Bl@&GÆs+hPʽhÎöº×h*go-tdx-guest-0.3.1/testing/testdata/sample_qeIdentity_response000066400000000000000000000011601456371376300246660ustar00rootroot00000000000000{"enclaveIdentity":{"id":"TD_QE","version":2,"issueDate":"2023-06-08T07:24:59Z","nextUpdate":"2023-07-08T07:24:59Z","tcbEvaluationDataNumber":15,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5","isvprodid":2,"tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"UpToDate"}]},"signature":"b6a601f05de27f2ca5105eec24bdd4bf7dd1b8bbfffc76dffe4f4d16b8a395843e4b92d430fd6744b0648bf44302c528412fcb9cbf3cc9ce6922a3057932b6a6"}go-tdx-guest-0.3.1/testing/testdata/sample_tcbInfo_response000066400000000000000000000045411456371376300241410ustar00rootroot00000000000000{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2023-06-18T08:42:58Z","nextUpdate":"2023-07-18T08:42:58Z","fmspc":"50806f000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":15,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477"]}]},"signature":"f6502d6fad1e3b7281df2b7eddc773d5b5281187346c12c5647b4f243cea49212be96a7a1a6b5d83e36323fe3fa9dacd61ebfbc38e631ff0fe29ef14ae0db0b4"} go-tdx-guest-0.3.1/testing/testdata/tdx_prod_quote_SPR_E4.dat000066400000000000000000000115561456371376300241650ustar00rootroot00000000000000“šr3÷œL©” ³•sœ?)*ºÎrcQ§ Ky/ÒyÁad©=Õ¿7=ƒC(Ô`¶“¯ž»†[²ÎÓ É¨›Hi©ú¶¾ ZScÆV@çcc¸6h£­•2xá‰WM2lgIûxªÍ“6’=¸o"ü¸ÜÔ¼Õá×!\»)'ÚpFÖ2fô20ÌIÀ>ò^¾Ib¨ØüÈ ô)v‚:ßø3Áå Qwœe“ó*,p ‹©¸Wƒø¾Ÿ¹D6G½À»);|Þ(°d“Ðo²­o•f©êm=æb6²¯5Üž*|o”GN4§üCí|cñº½ßc@È.T¨ÅÏâËvŸñòzÅ 4N`TH¤‹ÊmèñL’夓ÙA»\vÅaÐsrM‰u3÷%Ð/þlH \gó}!EÞ ‰[f …3ÿ·” ~>I•üàÕð¥õ³E•ì'á¤}  ]-----BEGIN CERTIFICATE----- MIIE8TCCBJegAwIBAgIVALumwXXYOLjfOQDMNBHyT1EtEEECMAoGCCqGSM49BAMC MHAxIjAgBgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoM EUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UE CAwCQ0ExCzAJBgNVBAYTAlVTMB4XDTIyMDkyMDEzMjAzMVoXDTI5MDkyMDEzMjAz MVowcDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBDZXJ0aWZpY2F0ZTEaMBgGA1UE CgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYD VQQIDAJDQTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARm lSnTiC8+htpOBB8ACzWhYy4ZdotUsgLHiCmQCbeOSo+ugeTLEMUaL5ppRB7BXZ3h cSTyl9J+qFRnStImB1cao4IDDDCCAwgwHwYDVR0jBBgwFoAUlW9dzb0b4elAScnU 9DPOAVcL3lQwawYDVR0fBGQwYjBgoF6gXIZaaHR0cHM6Ly9hcGkudHJ1c3RlZHNl cnZpY2VzLmludGVsLmNvbS9zZ3gvY2VydGlmaWNhdGlvbi92NC9wY2tjcmw/Y2E9 cGxhdGZvcm0mZW5jb2Rpbmc9ZGVyMB0GA1UdDgQWBBTNUGfKz6FNusOBAOtIlpN3 +HQArTAOBgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIwADCCAjkGCSqGSIb4TQEN AQSCAiowggImMB4GCiqGSIb4TQENAQEEEAid39ucA1nIKjvHcZI5V04wggFjBgoq hkiG+E0BDQECMIIBUzAQBgsqhkiG+E0BDQECAQIBAzAQBgsqhkiG+E0BDQECAgIB AzAQBgsqhkiG+E0BDQECAwIBAjAQBgsqhkiG+E0BDQECBAIBAjAQBgsqhkiG+E0B DQECBQIBAjAQBgsqhkiG+E0BDQECBgIBATAQBgsqhkiG+E0BDQECBwIBADAQBgsq hkiG+E0BDQECCAIBAjAQBgsqhkiG+E0BDQECCQIBADAQBgsqhkiG+E0BDQECCgIB ADAQBgsqhkiG+E0BDQECCwIBADAQBgsqhkiG+E0BDQECDAIBADAQBgsqhkiG+E0B DQECDQIBADAQBgsqhkiG+E0BDQECDgIBADAQBgsqhkiG+E0BDQECDwIBADAQBgsq hkiG+E0BDQECEAIBADAQBgsqhkiG+E0BDQECEQIBCzAfBgsqhkiG+E0BDQECEgQQ AwMCAgIBAAIAAAAAAAAAADAQBgoqhkiG+E0BDQEDBAIAADAUBgoqhkiG+E0BDQEE BAZQgG8AAAAwDwYKKoZIhvhNAQ0BBQoBATAeBgoqhkiG+E0BDQEGBBCMMU0X0gXf r8vsuwD8h+/3MEQGCiqGSIb4TQENAQcwNjAQBgsqhkiG+E0BDQEHAQEB/zAQBgsq hkiG+E0BDQEHAgEBADAQBgsqhkiG+E0BDQEHAwEB/zAKBggqhkjOPQQDAgNIADBF AiEAmjN1YKS+VLrRGw4/i+FzF784L6SghiDinPa01gVvw6YCIGSUY5WDzTNIYUAh nw1wE+bTlkY1MyH7qYg5xHkHB6rL -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICljCCAj2gAwIBAgIVAJVvXc29G+HpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg BgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs IENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex CzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB/7t21lXSO 2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z+UiRZCnqR7psOvgqFeSxlmTlJl eTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBS BgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy dmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d zb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB Af8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w+i6VYGW3UF/22uaXe0YJDj1Ue nA+TjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN+ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= -----END CERTIFICATE----- extra bytes(only for testing purpose) go-tdx-guest-0.3.1/testing/testdata/testdata.go000066400000000000000000000027051456371376300215130ustar00rootroot00000000000000// 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 testdata defines sample responses of the collaterals package testdata import ( _ "embed" ) // RawQuote contains raw bytes of quote. To be used only for testing // //go:embed "tdx_prod_quote_SPR_E4.dat" var RawQuote []byte // RawReport contains raw bytes of report. To be used only for testing // //go:embed "report.dat" var RawReport []byte // PckCrlBody contains sample PCK CRL. To be used only for testing // //go:embed "pckcrl" var PckCrlBody []byte // RootCrlBody contains sample Root CA CRL. To be used only for testing // //go:embed "rootcrl.der" var RootCrlBody []byte // TcbInfoBody contains sample TCBInfo response. To be used only for testing // //go:embed "sample_tcbInfo_response" var TcbInfoBody []byte // QeIdentityBody contains sample QeIdentity response. To be used only for testing // //go:embed "sample_qeIdentity_response" var QeIdentityBody []byte go-tdx-guest-0.3.1/tools/000077500000000000000000000000001456371376300152215ustar00rootroot00000000000000go-tdx-guest-0.3.1/tools/attest/000077500000000000000000000000001456371376300165255ustar00rootroot00000000000000go-tdx-guest-0.3.1/tools/attest/README.md000066400000000000000000000034411456371376300200060ustar00rootroot00000000000000# `attest` CLI tool This binary is a thin wrapper around the `client` library to gather attestation reports in either binary or textproto 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. ## Usage ``` ./attest [options...] ``` ### `-in` This flag provides a string of 64 bytes `REPORT_DATA` content directly on the command line to include in the output attestation report. REPORT_DATA can be either in base64 or hex format. If -inform=auto, first check with base64, hex and last with auto. ### `-inform` The format that input takes. One of * `base64`: for a byte string in base64 encoding. Fewer bytes than expected will be zero-filled. * `hex`: for a byte string encoded as a hexadecimal string. Fewer bytes than expected will be zero-filled. * `auto`: first check with base64 and last with hex Default value is `auto`. ### `-outform` The format that output takes. This can be `bin` for Intel's specified structures in binary 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. ### `verbose` If set, then the logger can append INFO and WARNING logs to stdout as per the verbosity level. Default logger has verbosity set to `0`, so verbosity option should be set to appropriate value to append INFO and WARN logs at variable verbosity levels to stdout. Default value is `false`. ### `verbosity` Used to set the verbosity of logger, where higher number means more verbose output. Default value is `0`.go-tdx-guest-0.3.1/tools/attest/attest.go000066400000000000000000000113701456371376300203620ustar00rootroot00000000000000// 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 main implements a CLI tool for collecting attestation reports. package main import ( "encoding/base64" "encoding/hex" "flag" "fmt" "io" "os" "strings" "unicode/utf8" "github.com/google/go-tdx-guest/client" labi "github.com/google/go-tdx-guest/client/linuxabi" pb "github.com/google/go-tdx-guest/proto/tdx" "github.com/google/logger" "google.golang.org/protobuf/encoding/prototext" ) var ( outform = flag.String("outform", "bin", "The format of the output attestation report. "+ "One of \"bin\", \"textproto\".") reportDataStr = flag.String("in", "", "A string of 64 bytes REPORT_DATA to include in the output attestation. "+ "REPORT_DATA can be either in base64 or hex format. If -inform=auto, first check with base64, hex and last with auto.") inform = flag.String("inform", "auto", "The format of the reportData input. One of base64, hex and auto. "+ "If -inform=auto, first check with base64 and last with hex.") 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.") verbosity = flag.Int("verbosity", 0, "The output verbosity. Higher number means more verbose output") ) func outputReport(data [labi.TdReportDataSize]byte, out io.Writer) error { tdxQuoteProvider, err := client.GetQuoteProvider() if err != nil { return err } if *outform == "bin" { bytes, err := client.GetRawQuote(tdxQuoteProvider, data) if err != nil { return err } out.Write(bytes) return nil } quote, err := client.GetQuote(tdxQuoteProvider, data) if err != nil { return err } return marshalAndWriteBytes(quote, out) } func marshalAndWriteBytes(quote any, out io.Writer) error { switch q := quote.(type) { case *pb.QuoteV4: bytes, err := prototext.Marshal(q) if err != nil { return err } out.Write(bytes) return nil default: return fmt.Errorf("unsupported quote type: %T", quote) } } 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 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 parseBytes(name string, in io.Reader, inform string, byteSize int) ([]byte, error) { inbytes, err := io.ReadAll(in) if err != nil { return nil, err } if len(inbytes) == 0 { return nil, nil } inByteStr := strings.TrimSpace(string(inbytes)) if !utf8.ValidString(inByteStr) { return nil, fmt.Errorf("could not decode %s contents as a UTF-8 string", name) } switch inform { case "base64": return sizedBytes(name, inByteStr, byteSize, base64.StdEncoding.DecodeString) case "hex": return sizedBytes(name, inByteStr, byteSize, hex.DecodeString) case "auto": // "auto" means to try base64 encoding first, then hex. if b, err := sizedBytes(name, inByteStr, byteSize, base64.StdEncoding.DecodeString); err == nil { return b, nil } return sizedBytes(name, inByteStr, byteSize, hex.DecodeString) default: return nil, fmt.Errorf("-inform should be either base64 or hex") } } func main() { flag.Parse() logger.Init("", *verbose, false, os.Stderr) logger.SetLevel(logger.Level(*verbosity)) reportData, err := parseBytes("-in", strings.NewReader(*reportDataStr), *inform, labi.TdReportDataSize) if err != nil { logger.Fatal(err) } if !(*outform == "bin" || *outform == "textproto") { logger.Fatalf("-outform is %s. Expect \"bin\" or \"textproto\"", *outform) } outwriter, filetoclose, err := outWriter() if err != nil { logger.Fatalf("failed to open output file: %v", err) } defer func() { if filetoclose != nil { filetoclose.Close() } }() var reportData64 [labi.TdReportDataSize]byte copy(reportData64[:], reportData) if err := outputReport(reportData64, outwriter); err != nil { logger.Fatal(err) } } go-tdx-guest-0.3.1/tools/check/000077500000000000000000000000001456371376300162765ustar00rootroot00000000000000go-tdx-guest-0.3.1/tools/check/README.md000066400000000000000000000035311456371376300175570ustar00rootroot00000000000000# `check` CLI tool This binary is a thin wrapper around the `verify` library to check Intel TDX quotes against expectations. The tool's input is an Intel TDX quote. The tool's output is an error or "Success". ## Usage ``` ./check [options...] ``` ### `-in` This flag provides the path to the quote to check. Stdin is "-". ### `-inform` The format that input takes. One of * `bin`: for a raw binary quote. * `proto`: A binary serialized `tdx.QuoteV4` message. * `textproto`: The `tdx.QuoteV4` message in textproto format. Default value is `bin`. ### `quiet` If set, doesn't write exit errors to Stdout. All results are communicated through exit code. ### `verbosity` Used to set the verbosity of logger, where higher number means more verbose output. Default value is `0`. ### `check_crl` Checks if the PCK certificate and the intermediate certificate of the PCK certificate chain has been revoked, and errors if so. Default `false`. Requires `-get_collateral` to be true so that CRLs are downloaded from the network. Note: For more details about PCK CRLs refer [Intel's PCK CRL specification](https://api.trustedservices.intel.com/documents/Intel_SGX_PCK_Certificate_CRL_Spec-1.5.pdf) ### `get_collateral` Uses the network to download "collateral" elements: * CRLs (if `-check_crl`) * The Intel quoting enclave (QE) Identity, and * TCB info from Intel's PCS. Default `false`. ## Examples The following example checks a binary quote, downloads collaterals, checks the quote against collaterals, and checks certificate revocations. ```shell $ ./check -in quote.dat -inform bin -get_collateral -check_crl ``` ## Exit code meaning * 0: Success * 1: Failure due to tool misuse * 2: Failure due to quote parsing errors, invalid signatures, certificates or collateral mismatch * 3: Failure due to an issue with the network or Intel's PCS go-tdx-guest-0.3.1/tools/check/check.go000066400000000000000000000344061456371376300177110ustar00rootroot00000000000000// 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 main implements a CLI tool for checking Intel TDX quotes. package main import ( "encoding/hex" "errors" "flag" "fmt" "io" "os" "strconv" "strings" "time" "github.com/google/go-sev-guest/tools/lib/cmdline" "github.com/google/go-tdx-guest/abi" ccpb "github.com/google/go-tdx-guest/proto/checkconfig" pb "github.com/google/go-tdx-guest/proto/tdx" testcases "github.com/google/go-tdx-guest/testing" "github.com/google/go-tdx-guest/validate" "github.com/google/go-tdx-guest/verify" "github.com/google/go-tdx-guest/verify/trust" "github.com/google/logger" "go.uber.org/multierr" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" ) const ( defaultCheckCrl = false defaultGetCollateral = false defaultTimeout = 2 * time.Minute defaultMaxRetryDelay = 30 * time.Second defaultMinQeSvn = 0 defaultMinPceSvn = 0 // Exit code 1 - tool usage error. exitTool = 1 // Exit code 2 - quote verification error. exitVerify = 2 // Exit code 3 - network error while downloading collateral. exitNetwork = 3 // Exit code 4 - the quote did not validate according to policy. exitPolicy = 4 ) var ( infile = flag.String("in", "-", "Path to the TDX quote to check. Stdin is \"-\".") inform = flag.String("inform", "bin", "The input format for the TDX quote. 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. By default, the file will be unmarshalled as binary," + " but if it ends in .textproto, it will be unmarshalled as prototext instead.")) quiet = flag.Bool("quiet", false, "If true, writes nothing the stdout or stderr. Success is exit code 0, failure exit code 1.") verbosity = flag.Int("verbosity", 0, "The output verbosity. Higher number means more verbose output") qevendoridS = flag.String("qe_vendor_id", "", "The expected QE_VENDOR_ID field as a hex string. Must encode 16 bytes. Unchecked if unset.") qevendorid = cmdline.Bytes("-qe_vendor_id", abi.QeVendorIDSize, qevendoridS) mrseamS = flag.String("mr_seam", "", "The expected MR_SEAM field as a hex string. Must encode 48 bytes. Unchecked if unset.") mrseam = cmdline.Bytes("-mr_seam", abi.MrSeamSize, mrseamS) tdattributesS = flag.String("td_attributes", "", "The expected TD_ATTRIBUTES field as a hex string. Must encode 8 bytes. Unchecked if unset.") tdattributes = cmdline.Bytes("-td_attributes", abi.TdAttributesSize, tdattributesS) xfamS = flag.String("xfam", "", "The expected XFAM field as a hex string. Must encode 8 bytes. Unchecked if unset.") xfam = cmdline.Bytes("-xfam", abi.XfamSize, xfamS) mrtdS = flag.String("mr_td", "", "The expected MR_TD field as a hex string. Must encode 48 bytes. Unchecked if unset.") mrtd = cmdline.Bytes("-mr_td", abi.MrTdSize, mrtdS) mrconfigidS = flag.String("mr_config_id", "", "The expected MR_CONFIG_ID field as a hex string. Must encode 48 bytes. Unchecked if unset.") mrconfigid = cmdline.Bytes("-mr_config_id", abi.MrConfigIDSize, mrconfigidS) mrownerS = flag.String("mr_owner", "", "The expected MR_OWNER field as a hex string. Must encode 48 bytes. Unchecked if unset.") mrowner = cmdline.Bytes("-mr_owner", abi.MrOwnerSize, mrownerS) mrownerconfigS = flag.String("mr_owner_config", "", "The expected MR_OWNER_CONFIG field as a hex string. Must encode 48 bytes. Unchecked if unset.") mrownerconfig = cmdline.Bytes("-mr_owner_config", abi.MrOwnerConfigSize, mrownerconfigS) 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) minteetcbsvnS = flag.String("minimum_tee_tcb_svn", "", "The minimum acceptable value for TEE_TCB_SVN field as a hex string. Must encode 16 bytes. Unchecked if unset.") minteetcbsvn = cmdline.Bytes("-minimum_tee_tcb_svn", abi.TeeTcbSvnSize, minteetcbsvnS) rtmrs = flag.String("rtmrs", "", "Comma-separated hex strings representing expected values of RTMRS field. Expected 4 strings, either empty or each must encode 48 bytes. Unchecked if unset") cabundles = flag.String("trusted_roots", "", "Comma-separated paths to CA bundles for the Intel TDX. Must be in PEM format, Root CA certificate. If unset, uses embedded root certificate.") // Optional Uint16. We don't want 0 to override the policy message, so instead of parsing // as Uint16 up front, we keep the flag a string and parse later if given. minqesvn = flag.String("minimum_qe_svn", "", "The minimum acceptable value for QE_SVN field.") minpcesvn = flag.String("minimum_pce_svn", "", "The minimum acceptable value for PCE_SVN field.") // Optional Bool checkcrl = flag.String("check_crl", "", "Download and check the CRL for revoked certificates. -get_collateral must be true.") getcollateral = flag.String("get_collateral", "", "If true, then permitted to download necessary collaterals for additional checks.") timeout = flag.Duration("timeout", defaultTimeout, "Duration to continue to retry failed HTTP requests.") maxRetryDelay = flag.Duration("max_retry_delay", defaultMaxRetryDelay, "Maximum Duration to wait between HTTP request retries.") testLocalGetter = flag.Bool("test_local_getter", false, "Use this flag only to test this CLI tool when network access is not available") // Assign the values of the flags to the corresponding proto fields config = &ccpb.Config{ RootOfTrust: &ccpb.RootOfTrust{}, Policy: &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}}, } ) func parseQuoteBytes(b []byte) (any, error) { quote, err := abi.QuoteToProto(b) if err != nil { return nil, fmt.Errorf("could not parse the TDX Quote from %q: %v", *infile, err) } return quote, nil } func parseQuote(b []byte) (any, error) { switch *inform { case "bin": return parseQuoteBytes(b) case "proto": result := &pb.QuoteV4{} if err := proto.Unmarshal(b, result); err != nil { return nil, fmt.Errorf("could not parse %q as proto: %v", *infile, err) } return result, nil case "textproto": result := &pb.QuoteV4{} if err := prototext.Unmarshal(b, result); err != nil { return nil, fmt.Errorf("could not parse %q as textproto: %v", *infile, err) } return result, nil default: return nil, fmt.Errorf("unknown value -inform=%s", *inform) } } func parseUint(p string, bits int) (uint64, error) { base := 10 prepped := p if strings.HasPrefix(strings.ToLower(p), "0x") { base = 16 prepped = prepped[2:] } else if strings.HasPrefix(strings.ToLower(p), "0o") { base = 8 prepped = prepped[2:] } else if strings.HasPrefix(strings.ToLower(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 readQuote() (any, 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 parseQuote(contents) } func dieWith(err error, exitCode int) { if !*quiet { fmt.Fprintf(os.Stderr, "FATAL: %v\n", err) } os.Exit(exitCode) } func die(err error) { dieWith(err, exitTool) } func parseConfig(path string) error { if path == "" { return nil } contents, err := os.ReadFile(path) 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 = &ccpb.RootOfTrust{} } if config.Policy == nil { config.Policy = &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}} } return nil } func parsePaths(s string) ([]string, error) { if s == "" { return nil, nil } paths := strings.Split(s, ",") 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 parseRtmrs(s string) ([][]byte, error) { if s == "" { return nil, nil } hexstrings := strings.Split(s, ",") var result [][]byte for _, hexstring := range hexstrings { h, err := hex.DecodeString(strings.TrimSpace(hexstring)) if err != nil { return nil, fmt.Errorf("could not parse RTMRS value as hex-encoded string: %q", hexstring) } result = append(result, h) } return result, nil } func setBool(value *bool, name, flag string, defaultValue bool) error { if flag == "" { if !configProtoPresent() { *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 !configProtoPresent() { *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 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 configProtoPresent() bool { return *configProto != "" } func populateRootOfTrust() error { rot := config.RootOfTrust if err := setBool(&rot.CheckCrl, "check_crl", *checkcrl, defaultCheckCrl); err != nil { return err } if err := setBool(&rot.GetCollateral, "get_collateral", *getcollateral, defaultGetCollateral); err != nil { return err } 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 setNonNil := func(dest *[]byte, value []byte) { if value != nil { *dest = value } } setRtmrs := func(dest *[][]byte, flag string) error { if flag != "" { val, err := parseRtmrs(flag) if err != nil { return err } *dest = val } return nil } setNonNil(&policy.HeaderPolicy.QeVendorId, *qevendorid) setNonNil(&policy.TdQuoteBodyPolicy.MinimumTeeTcbSvn, *minteetcbsvn) setNonNil(&policy.TdQuoteBodyPolicy.MrSeam, *mrseam) setNonNil(&policy.TdQuoteBodyPolicy.TdAttributes, *tdattributes) setNonNil(&policy.TdQuoteBodyPolicy.Xfam, *xfam) setNonNil(&policy.TdQuoteBodyPolicy.MrTd, *mrtd) setNonNil(&policy.TdQuoteBodyPolicy.MrConfigId, *mrconfigid) setNonNil(&policy.TdQuoteBodyPolicy.MrOwner, *mrowner) setNonNil(&policy.TdQuoteBodyPolicy.MrOwnerConfig, *mrownerconfig) setNonNil(&policy.TdQuoteBodyPolicy.ReportData, *reportdata) return multierr.Combine( setUint32(&policy.HeaderPolicy.MinimumQeSvn, "minimum_qe_svn", *minqesvn, defaultMinQeSvn), setUint32(&policy.HeaderPolicy.MinimumPceSvn, "minimum_pce_svn", *minpcesvn, defaultMinPceSvn), setRtmrs(&policy.TdQuoteBodyPolicy.Rtmrs, *rtmrs), ) } func main() { logger.Init("", false, false, os.Stdout) flag.Parse() cmdline.Parse("auto") logger.SetLevel(logger.Level(*verbosity)) logger.V(1).Info("Parsing input parameters") if err := parseConfig(*configProto); err != nil { die(err) } if err := multierr.Combine(populateRootOfTrust(), populateConfig()); err != nil { die(err) } if config.RootOfTrust.CheckCrl && !config.RootOfTrust.GetCollateral { die(errors.New("cannot specify both -check_crl=true and -get_collateral=false")) } quote, err := readQuote() if err != nil { die(err) } logger.V(1).Info("TDX Quote parsed successfully") sopts, err := verify.RootOfTrustToOptions(config.RootOfTrust) if err != nil { die(err) } logger.V(1).Info("Input parameters parsed successfully") var getter trust.HTTPSGetter getter = &trust.SimpleHTTPSGetter{} if *testLocalGetter { getter = testcases.TestGetter } sopts.Getter = &trust.RetryHTTPSGetter{ Timeout: *timeout, MaxRetryDelay: *maxRetryDelay, Getter: getter, } logger.V(1).Info("Verifying the TDX Quote from input") if err := verify.TdxQuote(quote, 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 crlNetworkErr *verify.CRLUnavailableErr var collateralNetworkErr *trust.AttestationRecreationErr if errors.As(err, &crlNetworkErr) || errors.As(err, &collateralNetworkErr) { exitCode = exitNetwork return true } return false } if !clarify(err) { clarify(errors.Unwrap(err)) } dieWith(fmt.Errorf("could not verify the TDX Quote: %v", err), exitCode) } logger.Info("TDX Quote verified successfully") opts, err := validate.PolicyToOptions(config.Policy) if err != nil { die(err) } if err := validate.TdxQuote(quote, opts); err != nil { dieWith(fmt.Errorf("error validating the TDX Quote: %v", err), exitPolicy) } logger.V(1).Info("TDX Quote validated successfully") } go-tdx-guest-0.3.1/tools/check/check_test.go000066400000000000000000000311541456371376300207450ustar00rootroot00000000000000// 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 main import ( "encoding/hex" "fmt" "os" "os/exec" "strconv" "testing" ccpb "github.com/google/go-tdx-guest/proto/checkconfig" "github.com/google/logger" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" ) // 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 *ccpb.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 check string func TestMain(m *testing.M) { if output, err := exec.Command("go", "build", "-buildvcs=false", ".").CombinedOutput(); err != nil { die(fmt.Errorf("could not build check tool: %v, %s", err, output)) } check = "./check" logger.Init("CheckTestLog", false, false, os.Stderr) os.Exit(m.Run()) } func withBaseArgs(config string, args ...string) []string { base := []string{ "-in", "../../testing/testdata/tdx_prod_quote_SPR_E4.dat", "-test_local_getter", } if config != "" { base = append(base, fmt.Sprintf("-config=%s", config)) } result := make([]string, len(args)+len(base)) copy(result, base) copy(result[len(base):], args) return result } func setField(p *ccpb.Policy, policy string, name string, value any) { if policy == "header_policy" { s := p.HeaderPolicy r := s.ProtoReflect() ty := r.Descriptor() r.Set(ty.Fields().ByName(protoreflect.Name(name)), protoreflect.ValueOf(value)) } else if policy == "td_quote_body_policy" { s := p.TdQuoteBodyPolicy r := s.ProtoReflect() ty := r.Descriptor() r.Set(ty.Fields().ByName(protoreflect.Name(name)), protoreflect.ValueOf(value)) } } func bytesSetter(name string, policy string) setterFn { return func(p *ccpb.Policy, value string, _ *testing.T) bool { v, err := hex.DecodeString(value) if err != nil { return true } setField(p, policy, name, v) return false } } func uint32setter(name string, policy string) setterFn { return func(p *ccpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 32) if err != nil { return true } setField(p, policy, name, uint32(u)) return false } } func testCases() []testCase { return []testCase{ { flag: "minimum_qe_svn", good: "0", bad: []string{ "1", "21", "666666", }, setter: uint32setter("minimum_qe_svn", "header_policy"), }, { flag: "minimum_pce_svn", good: "0", bad: []string{ "121", // right size "666666", // wrong size }, setter: uint32setter("minimum_pce_svn", "header_policy"), }, { flag: "qe_vendor_id", good: "939a7233f79c4ca9940a0db3957f0607", bad: []string{"00000000000000000000000000000001"}, setter: bytesSetter("qe_vendor_id", "header_policy"), }, { flag: "minimum_tee_tcb_svn", good: "03000400000000000000000000000000", bad: []string{"03000400000000000000000000000011"}, setter: bytesSetter("minimum_tee_tcb_svn", "td_quote_body_policy"), }, { flag: "mr_seam", good: "2fd279c16164a93dd5bf373d834328d46008c2b693af9ebb865b08b2ced320c9a89b4869a9fab60fbe9d0c5a5363c656", bad: []string{ "2fd279c16164a93dd5bf373d834328d46008c2b693af9ebb865b08b2ced320c9a89b4869a9fab60fbe9d0c5a5363c326"}, setter: bytesSetter("mr_seam", "td_quote_body_policy"), }, { flag: "td_attributes", good: "0000004000000000", bad: []string{"0000004000000011"}, setter: bytesSetter("td_attributes", "td_quote_body_policy"), }, { flag: "xfam", good: "e71a060000000000", bad: []string{}, setter: bytesSetter("xfam", "td_quote_body_policy"), }, { flag: "mr_td", good: "6363b8043668a3ad953278e10389574d326c6749fb78aa810ecd9336923db86f22fc00b8dcd404bc10d5e119d7215cbb", bad: []string{ "6363b8043668a3ad953278e10389574d326c6749fb78aa810ecd9336923db86f22fc00b8dcd404bc10d5e119d1115cbb", "6363b8000000a3ad953278e10389574d326c6749fb78aa810ecd9336923db86f22fc00b8dcd404bc10d5e119d7215cbb", }, setter: bytesSetter("mr_td", "td_quote_body_policy"), }, { flag: "mr_config_id", good: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", bad: []string{"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011"}, setter: bytesSetter("mr_config_id", "td_quote_body_policy"), }, { flag: "mr_owner", good: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", bad: []string{"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022"}, setter: bytesSetter("mr_owner", "td_quote_body_policy"), }, { flag: "mr_owner_config", good: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", bad: []string{"0", "not a hex"}, setter: bytesSetter("mr_owner_config", "td_quote_body_policy"), }, { flag: "report_data", good: "6c62dec1b8191749a31dab490be532a35944dea47caef1f980863993d9899545eb7406a38d1eed313b987a467dacead6f0c87a6d766c66f6f29f8acb281f1113", bad: []string{"6c62dec1b8191749a31dab490be532a35944dea47caef1f980863993d9899545eb7406a38d1eed313b987a467dacead6f0c87a6d766c66f6f29f8acb281f2213"}, setter: bytesSetter("report_data", "td_quote_body_policy"), }, } } // 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 *ccpb.Policy, t *testing.T, runner func(path string)) { config := &ccpb.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))...) 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))...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s succeeded unexpectedly: %s", cmd, output) } }) } } } func TestRtmrs(t *testing.T) { validRtmrs := []string{ "2927da70461cd63266f43230cc1849c03ef25ebe490062a801d8fcc80af42976823adf08f833c1e50b51779c6593f32a,2c700b8ba9b85783f8be9fb9443647bdc0bb3c50747f06297cc6538c25a5f589c4b56d035c59107c6bc5800db2cacb61,8652f0caaba7e215ea442dc36a4499d8fec3362f3a0b2ca151cbe4b3e6466fe59c7368b3c2287fc7c3bf5c924eb4424e,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "2927da70461cd63266f43230cc1849c03ef25ebe490062a801d8fcc80af42976823adf08f833c1e50b51779c6593f32a,2c700b8ba9b85783f8be9fb9443647bdc0bb3c50747f06297cc6538c25a5f589c4b56d035c59107c6bc5800db2cacb61,,", ",,,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", } invalidRtmrs := []string{ ",", "0,0,0,0", "0,0,", } for _, tc := range validRtmrs { t.Run("valid_rtmrs", func(t *testing.T) { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", "rtmrs", tc))...) if output, err := cmd.CombinedOutput(); err != nil { t.Errorf("%s failed unexpectedly: %v (%s)", cmd, err, output) } }) } for _, tc := range invalidRtmrs { t.Run("invalid_rtmrs", func(t *testing.T) { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", "rtmrs", tc))...) 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 := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}} if tc.setter(p, tc.good, t) { t.Fatal("unexpected parse failure") } withTestConfig(p, t, func(path string) { cmd := exec.Command(check, withBaseArgs(path)...) 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 := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}} if tc.setter(p, bad, t) { return } withTestConfig(p, t, func(path string) { cmd := exec.Command(check, withBaseArgs(path)...) 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 := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}} 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))...) 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 := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}} 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))...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s (%v) succeeded unexpectedly: %s", cmd, p, output) } }) }) } } } func TestNetworkFlags(t *testing.T) { // check_crl = true should fail if get_collateral = false cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", "check_crl", "true"))...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s check_crl flag succeeded unexpectedly: %v, %s", cmd, err, output) } } func TestCaBundles(t *testing.T) { correctPath := []string{"../../verify/trusted_root.pem", "../../verify/trusted_root.pem,../../verify/trusted_root.pem"} fakePath := []string{"fake_path"} for _, path := range correctPath { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", "trusted_roots", path))...) if output, err := cmd.CombinedOutput(); err != nil { t.Errorf("%s correct path for trusted roots failed unexpectedly: %v, %s", cmd, err, output) } } for _, path := range fakePath { cmd := exec.Command(check, withBaseArgs("", fmt.Sprintf("-%s=%s", "trusted_roots", path))...) if output, err := cmd.CombinedOutput(); err == nil { t.Errorf("%s fake path for trusted roots succeeded unexpectedly: %s", cmd, output) } } } go-tdx-guest-0.3.1/validate/000077500000000000000000000000001456371376300156525ustar00rootroot00000000000000go-tdx-guest-0.3.1/validate/validate.go000066400000000000000000000346661456371376300200110ustar00rootroot00000000000000// 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 validate provides the library functions to validate a TDX quote package validate import ( "bytes" "encoding/binary" "encoding/hex" "fmt" "github.com/google/go-tdx-guest/abi" ccpb "github.com/google/go-tdx-guest/proto/checkconfig" pb "github.com/google/go-tdx-guest/proto/tdx" vr "github.com/google/go-tdx-guest/verify" "github.com/google/logger" "go.uber.org/multierr" ) const ( // If bit X is 1 in xfamFixed1, it must be 1 in any xfam. xfamFixed1 = 0x00000003 // If bit X is 0 in xfamFixed0, it must be 0 in any xfam. xfamFixed0 = 0x0006DBE7 // If bit X is 1 in tdAttributesFixed1, it must be 1 in any tdAttributes. tdAttributesFixed1 = 0x0 tdxAttributesSeptVeDisSupport = 1 << 28 tdxAttributesPksSupport = 1 << 30 tdxAttributesPerfmonSupport = 1 << 63 // Supported ATTRIBUTES bits depend on the supported features - bits 0 (DEBUG), 30 (PKS), 63 (PERFMON) // and 28 (SEPT VE DISABLE) // If bit X is 0 in tdAttributesFixed0, it must be 0 in any tdAttributes. tdAttributesFixed0 = 0x1 | tdxAttributesSeptVeDisSupport | tdxAttributesPksSupport | tdxAttributesPerfmonSupport rtmrsCount = 4 ) // Options represents validation options for a TDX attestation Quote. type Options struct { HeaderOptions HeaderOptions TdQuoteBodyOptions TdQuoteBodyOptions } // HeaderOptions represents validation options for a TDX attestation Quote Header. type HeaderOptions struct { // MinimumQeSvn is the minimum QE security version number. Not checked if nil. MinimumQeSvn uint16 // MinimumPceSvn is the minimum PCE security version number. Not checked if nil. MinimumPceSvn uint16 // QeVendorID is the expected QE_VENDOR_ID field. Must be nil or 16 bytes long. Not checked if nil. QeVendorID []byte } // TdQuoteBodyOptions represents validation options for a TDX attestation Quote's TD Quote body. type TdQuoteBodyOptions struct { // MinimumTeeTcbSvn is the component-wise minimum TEE_TCB security version number. Must be nil or 16 bytes long. Not checked if nil. MinimumTeeTcbSvn []byte // MrSeam is the expected MR_SEAM field. Must be nil or 48 bytes long. Not checked if nil. MrSeam []byte // TdAttributes is the expected TD_ATTRIBUTES field. Must be nil or 8 bytes long. Not checked if nil. TdAttributes []byte // Xfam is the expected XFAM field. Must be nil or 8 bytes long. Not checked if nil. Xfam []byte // MrTd is the expected MR_TD field. Must be nil or 48 bytes long. Not checked if nil. MrTd []byte // MrConfigID is the expected MR_CONFIG_ID field. Must be nil or 48 bytes long. Not checked if nil. MrConfigID []byte // MrOwner is the expected MR_OWNER field. Must be nil or 48 bytes long. Not checked if nil. MrOwner []byte // MrOwnerConfig is the expected MR_OWNER_CONFIG field. Must be nil or 48 bytes long. Not checked if nil. MrOwnerConfig []byte // Rtmrs is the expected RTMRS field. Must be nil or 48 * rtmrsCount. Not checked if nil. Rtmrs [][]byte // ReportData is the expected REPORT_DATA field. Must be nil or 64 bytes long. Not checked if nil. ReportData []byte } 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 lengthCheckRtmr(name string, size int, length int, value [][]byte) error { if len(value) == 0 { return nil } if len(value) != size { return fmt.Errorf("option %q size is %d. Want %d", name, len(value), size) } for i := range value { if len(value[i]) != 0 && len(value[i]) != length { return fmt.Errorf("option %q[%d] length is %d. Want %d", name, i, len(value), length) } } return nil } func checkOptionsLengths(opts *Options) error { return multierr.Combine( lengthCheck("mr_seam", abi.MrSeamSize, opts.TdQuoteBodyOptions.MrSeam), lengthCheck("td_attributes", abi.TdAttributesSize, opts.TdQuoteBodyOptions.TdAttributes), lengthCheck("xfam", abi.XfamSize, opts.TdQuoteBodyOptions.Xfam), lengthCheck("mr_td", abi.MrTdSize, opts.TdQuoteBodyOptions.MrTd), lengthCheck("mr_config_id", abi.MrConfigIDSize, opts.TdQuoteBodyOptions.MrConfigID), lengthCheck("mr_owner", abi.MrOwnerSize, opts.TdQuoteBodyOptions.MrOwner), lengthCheck("mr_owner_config", abi.MrOwnerConfigSize, opts.TdQuoteBodyOptions.MrOwnerConfig), lengthCheck("report_data", abi.ReportDataSize, opts.TdQuoteBodyOptions.ReportData), lengthCheck("qe_vendor_id", abi.QeVendorIDSize, opts.HeaderOptions.QeVendorID), lengthCheckRtmr("rtmrs", rtmrsCount, abi.RtmrSize, opts.TdQuoteBodyOptions.Rtmrs), ) } // PolicyToOptions returns an Options object that is represented by a Policy message. func PolicyToOptions(policy *ccpb.Policy) (*Options, error) { if policy.GetHeaderPolicy().GetMinimumQeSvn() > 65535 { return nil, fmt.Errorf("minimum_qe_svn is %d. Expect 0-65535", policy.GetHeaderPolicy().GetMinimumQeSvn()) } if policy.GetHeaderPolicy().GetMinimumPceSvn() > 65535 { return nil, fmt.Errorf("minimum_pce_svn is %d. Expect 0-65535", policy.GetHeaderPolicy().GetMinimumPceSvn()) } opts := &Options{ HeaderOptions: HeaderOptions{ MinimumQeSvn: uint16(policy.GetHeaderPolicy().GetMinimumQeSvn()), MinimumPceSvn: uint16(policy.GetHeaderPolicy().GetMinimumPceSvn()), QeVendorID: policy.GetHeaderPolicy().GetQeVendorId(), }, TdQuoteBodyOptions: TdQuoteBodyOptions{ MinimumTeeTcbSvn: policy.GetTdQuoteBodyPolicy().GetMinimumTeeTcbSvn(), MrSeam: policy.GetTdQuoteBodyPolicy().GetMrSeam(), TdAttributes: policy.GetTdQuoteBodyPolicy().GetTdAttributes(), Xfam: policy.GetTdQuoteBodyPolicy().GetXfam(), MrTd: policy.GetTdQuoteBodyPolicy().GetMrTd(), MrConfigID: policy.GetTdQuoteBodyPolicy().GetMrConfigId(), MrOwner: policy.GetTdQuoteBodyPolicy().GetMrOwner(), MrOwnerConfig: policy.GetTdQuoteBodyPolicy().GetMrOwnerConfig(), Rtmrs: policy.GetTdQuoteBodyPolicy().GetRtmrs(), ReportData: policy.GetTdQuoteBodyPolicy().GetReportData(), }, } if err := checkOptionsLengths(opts); err != nil { return nil, err } return opts, nil } func byteCheckRtmr(size int, given, required [][]byte) error { if len(required) == 0 { logger.V(1).Info("Skipping validation check for RTMRs field: input provided is nil") return nil } if len(required) != rtmrsCount { return fmt.Errorf("RTMR field size(%d) is not equal to expected size(4)", len(required)) } for i := range required { if len(required[i]) == 0 { logger.V(1).Infof("Skipping validation check for RTMR[%d] field: input provided is nil", i+1) continue } if len(required[i]) != size { return fmt.Errorf("RTMR[%d] should be 48 bytes, found %d", i, len(required[i])) } logger.V(2).Infof("Quote field RTMR[%d] value is %s, and expected value is %s", i+1, hex.EncodeToString(given[i]), hex.EncodeToString(required[i])) if !bytes.Equal(required[i], given[i]) { return fmt.Errorf("quote field RTMR[%d] is %s. Expect %s", i, hex.EncodeToString(given[i]), hex.EncodeToString(required[i])) } logger.V(1).Infof("Successfully validated RTMR[%d] field", i+1) } return nil } func byteCheck(option, field string, size int, given, required []byte) error { if len(required) == 0 { logger.V(1).Infof("Skipping validation check for %s field: input provided is nil", field) return nil } if len(required) != size { return fmt.Errorf("option %v must be nil or %d bytes", option, size) } logger.V(2).Infof("Quote field %s value is %s, and expected value is %s", field, hex.EncodeToString(given), hex.EncodeToString(required)) if !bytes.Equal(required, given) { return fmt.Errorf("quote field %s is %s. Expect %s", field, hex.EncodeToString(given), hex.EncodeToString(required)) } logger.V(1).Infof("Successfully validated %s field", field) return nil } func exactByteMatch(quote *pb.QuoteV4, opts *Options) error { givenRtmr := quote.GetTdQuoteBody().GetRtmrs() return multierr.Combine( byteCheck("MrSeam", "MR_SEAM", abi.MrSeamSize, quote.GetTdQuoteBody().GetMrSeam(), opts.TdQuoteBodyOptions.MrSeam), byteCheck("TdAttributes", "TD_ATTRIBUTES", abi.TdAttributesSize, quote.GetTdQuoteBody().GetTdAttributes(), opts.TdQuoteBodyOptions.TdAttributes), byteCheck("Xfam", "XFAM", abi.XfamSize, quote.GetTdQuoteBody().GetXfam(), opts.TdQuoteBodyOptions.Xfam), byteCheck("MrTd", "MR_TD", abi.MrTdSize, quote.GetTdQuoteBody().GetMrTd(), opts.TdQuoteBodyOptions.MrTd), byteCheck("MrConfigID", "MR_CONFIG_ID", abi.MrConfigIDSize, quote.GetTdQuoteBody().GetMrConfigId(), opts.TdQuoteBodyOptions.MrConfigID), byteCheck("MrOwner", "MR_OWNER", abi.MrOwnerSize, quote.GetTdQuoteBody().GetMrOwner(), opts.TdQuoteBodyOptions.MrOwner), byteCheck("MrOwnerConfig", "MR_OWNER_CONFIG", abi.MrOwnerConfigSize, quote.GetTdQuoteBody().GetMrOwnerConfig(), opts.TdQuoteBodyOptions.MrOwnerConfig), byteCheckRtmr(abi.RtmrSize, givenRtmr, opts.TdQuoteBodyOptions.Rtmrs), byteCheck("ReportData", "REPORT_DATA", abi.ReportDataSize, quote.GetTdQuoteBody().GetReportData(), opts.TdQuoteBodyOptions.ReportData), byteCheck("QeVendorID", "QE_VENDOR_ID", abi.QeVendorIDSize, quote.GetHeader().GetQeVendorId(), opts.HeaderOptions.QeVendorID), ) } func isSvnHigherOrEqual(quoteSvn []byte, optionSvn []byte) bool { if optionSvn == nil { return true } for i := range quoteSvn { if quoteSvn[i] < optionSvn[i] { return false } } return true } func minVersionCheck(quote *pb.QuoteV4, opts *Options) error { logger.V(1).Info("Setting the minimum_qe_svn parameter value to ", opts.HeaderOptions.MinimumQeSvn) logger.V(1).Info("Setting the minimum_pce_svn parameter value to ", opts.HeaderOptions.MinimumPceSvn) logger.V(1).Info("Setting the minimum_tee_tcb_svn parameter value to ", opts.TdQuoteBodyOptions.MinimumTeeTcbSvn) logger.V(2).Infof("TEE TCB security-version number is %v, and minimum_tee_tcb_svn value is %v", quote.GetTdQuoteBody().GetTeeTcbSvn(), opts.TdQuoteBodyOptions.MinimumTeeTcbSvn) if !isSvnHigherOrEqual(quote.GetTdQuoteBody().GetTeeTcbSvn(), opts.TdQuoteBodyOptions.MinimumTeeTcbSvn) { return fmt.Errorf("TEE TCB security-version number %d is less than the required minimum %d", quote.GetTdQuoteBody().GetTeeTcbSvn(), opts.TdQuoteBodyOptions.MinimumTeeTcbSvn) } logger.V(1).Info("Successfully validated TEE TCB security-version number") qeSvn := binary.LittleEndian.Uint16(quote.GetHeader().GetQeSvn()) pceSvn := binary.LittleEndian.Uint16(quote.GetHeader().GetPceSvn()) logger.V(2).Infof("QE security-version number is %d, and minimum_qe_svn value is %d", qeSvn, opts.HeaderOptions.MinimumQeSvn) if qeSvn < opts.HeaderOptions.MinimumQeSvn { return fmt.Errorf("QE security-version number %d is less than the required minimum %d", qeSvn, opts.HeaderOptions.MinimumQeSvn) } logger.V(1).Info("Successfully validated QE security-version number") logger.V(2).Infof("PCE security-version number is %d, and minimum_pce_svn value is %d", pceSvn, opts.HeaderOptions.MinimumPceSvn) if pceSvn < opts.HeaderOptions.MinimumPceSvn { return fmt.Errorf("PCE security-version number %d is less than the required minimum %d", pceSvn, opts.HeaderOptions.MinimumPceSvn) } logger.V(1).Info("Successfully validated PCE security-version number") return nil } func validateXfam(value []byte, fixed1, fixed0 uint64) error { if len(value) == 0 { return nil } if len(value) != abi.XfamSize { return fmt.Errorf("xfam size is invalid") } xfam := binary.LittleEndian.Uint64(value[:]) logger.V(2).Infof("XFAM value is %v, XFAMFixed0 value is %v and XFAMFixed1 value is %v", xfam, fixed0, fixed1) if xfam&fixed1 != fixed1 { return fmt.Errorf("unauthorized xfam 0x%x as xfamFixed1 0x%x bits are unset", xfam, fixed1) } if xfam&(^fixed0) != 0 { return fmt.Errorf("unauthorized xfam 0x%x as xfamFixed0 0x%x bits are set", xfam, fixed0) } logger.V(1).Info("Successfully validated XFAM field") return nil } func validateTdAttributes(value []byte, fixed1, fixed0 uint64) error { if len(value) == 0 { return nil } if len(value) != abi.TdAttributesSize { return fmt.Errorf("tdAttributes size is invalid") } tdAttributes := binary.LittleEndian.Uint64(value[:]) logger.V(2).Infof("TdAttributes value is %v, TdAttributesFixed0 value is %v and TdAttributesFixed1 value is %v", tdAttributes, fixed0, fixed1) if tdAttributes&fixed1 != fixed1 { return fmt.Errorf("unauthorized tdAttributes 0x%x as tdAttributesFixed1 0x%x bits are unset", tdAttributes, fixed1) } if tdAttributes&(^fixed0) != 0 { return fmt.Errorf("unauthorized tdAttributes 0x%x as tdAttributesFixed0 0x%x bits are set", tdAttributes, fixed0) } logger.V(1).Info("Successfully validated TdAttributes field") return nil } // TdxQuote validates fields of the protobuf representation of an attestation Quote // against expectations depending on supported quote formats - QuoteV4. // Does not check the attestation certificates or signature. func TdxQuote(quote any, options *Options) error { if options == nil { return vr.ErrOptionsNil } switch q := quote.(type) { case *pb.QuoteV4: return tdxQuoteV4(q, options) default: return fmt.Errorf("unsupported quote type: %T", quote) } } // tdxQuoteV4 validates QuoteV4 fields of the protobuf representation of an attestation Quote // against expectations. Does not check the attestation certificates or signature. func tdxQuoteV4(quote *pb.QuoteV4, options *Options) error { if err := abi.CheckQuoteV4(quote); err != nil { return fmt.Errorf("QuoteV4 invalid: %v", err) } logger.V(1).Info("Validating the TDX Quote using input parameters") return multierr.Combine( exactByteMatch(quote, options), minVersionCheck(quote, options), validateXfam(quote.GetTdQuoteBody().GetXfam(), xfamFixed1, xfamFixed0), validateTdAttributes(quote.GetTdQuoteBody().GetTdAttributes(), tdAttributesFixed1, tdAttributesFixed0), ) } // RawTdxQuote checks the raw bytes representation of an attestation quote. func RawTdxQuote(raw []byte, options *Options) error { quote, err := abi.QuoteToProto(raw) if err != nil { return fmt.Errorf("could not convert raw bytes to QuoteV4: %v", err) } return TdxQuote(quote, options) } go-tdx-guest-0.3.1/validate/validate_test.go000066400000000000000000000222161456371376300210340ustar00rootroot00000000000000// 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 validate import ( "strings" "testing" pb "github.com/google/go-tdx-guest/proto/tdx" vr "github.com/google/go-tdx-guest/verify" "github.com/google/go-tdx-guest/abi" "github.com/google/go-tdx-guest/testing/testdata" ) func convert(a []byte, x byte) []byte { for i := range a { a[i] = x } return a } func TestTdxQuote(t *testing.T) { if err := TdxQuote(nil, nil); err != vr.ErrOptionsNil { t.Error(err) } qeSvn := uint16(0) pceSvn := uint16(0) qeVendorID := []byte{0x93, 0x9a, 0x72, 0x33, 0xf7, 0x9c, 0x4c, 0xa9, 0x94, 0xa, 0xd, 0xb3, 0x95, 0x7f, 0x6, 0x7} teeTcbSvn := []byte{0x3, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} mrSeam := []byte{0x2f, 0xd2, 0x79, 0xc1, 0x61, 0x64, 0xa9, 0x3d, 0xd5, 0xbf, 0x37, 0x3d, 0x83, 0x43, 0x28, 0xd4, 0x60, 0x8, 0xc2, 0xb6, 0x93, 0xaf, 0x9e, 0xbb, 0x86, 0x5b, 0x8, 0xb2, 0xce, 0xd3, 0x20, 0xc9, 0xa8, 0x9b, 0x48, 0x69, 0xa9, 0xfa, 0xb6, 0xf, 0xbe, 0x9d, 0xc, 0x5a, 0x53, 0x63, 0xc6, 0x56} tdAttributes := []byte{0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0} xfam := []byte{0xe7, 0x1a, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0} mrTd := []byte{0x63, 0x63, 0xb8, 0x4, 0x36, 0x68, 0xa3, 0xad, 0x95, 0x32, 0x78, 0xe1, 0x3, 0x89, 0x57, 0x4d, 0x32, 0x6c, 0x67, 0x49, 0xfb, 0x78, 0xaa, 0x81, 0xe, 0xcd, 0x93, 0x36, 0x92, 0x3d, 0xb8, 0x6f, 0x22, 0xfc, 0x0, 0xb8, 0xdc, 0xd4, 0x4, 0xbc, 0x10, 0xd5, 0xe1, 0x19, 0xd7, 0x21, 0x5c, 0xbb} mrConfigID := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} mrOwner := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} mrOwnerConfig := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} rtmr0 := []byte{0x29, 0x27, 0xda, 0x70, 0x46, 0x1c, 0xd6, 0x32, 0x66, 0xf4, 0x32, 0x30, 0xcc, 0x18, 0x49, 0xc0, 0x3e, 0xf2, 0x5e, 0xbe, 0x49, 0x0, 0x62, 0xa8, 0x1, 0xd8, 0xfc, 0xc8, 0xa, 0xf4, 0x29, 0x76, 0x82, 0x3a, 0xdf, 0x8, 0xf8, 0x33, 0xc1, 0xe5, 0xb, 0x51, 0x77, 0x9c, 0x65, 0x93, 0xf3, 0x2a} rtmr1 := []byte{0x2c, 0x70, 0xb, 0x8b, 0xa9, 0xb8, 0x57, 0x83, 0xf8, 0xbe, 0x9f, 0xb9, 0x44, 0x36, 0x47, 0xbd, 0xc0, 0xbb, 0x3c, 0x50, 0x74, 0x7f, 0x6, 0x29, 0x7c, 0xc6, 0x53, 0x8c, 0x25, 0xa5, 0xf5, 0x89, 0xc4, 0xb5, 0x6d, 0x3, 0x5c, 0x59, 0x10, 0x7c, 0x6b, 0xc5, 0x80, 0xd, 0xb2, 0xca, 0xcb, 0x61} rtmr2 := []byte{0x86, 0x52, 0xf0, 0xca, 0xab, 0xa7, 0xe2, 0x15, 0xea, 0x44, 0x2d, 0xc3, 0x6a, 0x44, 0x99, 0xd8, 0xfe, 0xc3, 0x36, 0x2f, 0x3a, 0xb, 0x2c, 0xa1, 0x51, 0xcb, 0xe4, 0xb3, 0xe6, 0x46, 0x6f, 0xe5, 0x9c, 0x73, 0x68, 0xb3, 0xc2, 0x28, 0x7f, 0xc7, 0xc3, 0xbf, 0x5c, 0x92, 0x4e, 0xb4, 0x42, 0x4e} rtmr3 := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} reportData := []byte{0x6c, 0x62, 0xde, 0xc1, 0xb8, 0x19, 0x17, 0x49, 0xa3, 0x1d, 0xab, 0x49, 0xb, 0xe5, 0x32, 0xa3, 0x59, 0x44, 0xde, 0xa4, 0x7c, 0xae, 0xf1, 0xf9, 0x80, 0x86, 0x39, 0x93, 0xd9, 0x89, 0x95, 0x45, 0xeb, 0x74, 0x6, 0xa3, 0x8d, 0x1e, 0xed, 0x31, 0x3b, 0x98, 0x7a, 0x46, 0x7d, 0xac, 0xea, 0xd6, 0xf0, 0xc8, 0x7a, 0x6d, 0x76, 0x6c, 0x66, 0xf6, 0xf2, 0x9f, 0x8a, 0xcb, 0x28, 0x1f, 0x11, 0x13} mknonce := func(front []byte) []byte { result := make([]byte, 64) copy(result[:], front) return result } make2darray := func(size int) [][]byte { a := make([][]byte, 4) for i := range a { a[i] = make([]byte, size) } return a } nonce12345 := mknonce([]byte{1, 2, 3, 4, 5}) quoteFn := func(nonce []byte) any { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } data := make([]byte, abi.ReportDataSize) copy(data, nonce[:]) switch q := quote.(type) { case *pb.QuoteV4: q.TdQuoteBody.ReportData = data default: t.Fatal("unsupported quote type") } return quote } quoteSample := quoteFn(reportData) quote12345 := quoteFn(nonce12345) type testCase struct { name string quote any opts *Options wantErr string } tests := []testCase{ { name: "deep check", quote: quoteSample, opts: &Options{ HeaderOptions: HeaderOptions{ MinimumQeSvn: qeSvn, MinimumPceSvn: pceSvn, QeVendorID: qeVendorID, }, TdQuoteBodyOptions: TdQuoteBodyOptions{ MinimumTeeTcbSvn: teeTcbSvn, MrSeam: mrSeam, TdAttributes: tdAttributes, Xfam: xfam, MrTd: mrTd, MrConfigID: mrConfigID, MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, Rtmrs: [][]byte{rtmr0, rtmr1, rtmr2, rtmr3}, ReportData: reportData, }, }, }, { name: "min QE security-version check", quote: quote12345, opts: &Options{ HeaderOptions: HeaderOptions{ MinimumQeSvn: uint16(2), }, }, wantErr: "QE security-version number 0 is less than the required minimum 2", }, { name: "min Pce security-version check", quote: quote12345, opts: &Options{ HeaderOptions: HeaderOptions{ MinimumPceSvn: uint16(2), }, }, wantErr: "PCE security-version number 0 is less than the required minimum 2", }, { name: "min TEE TCB security-version check", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ MinimumTeeTcbSvn: []byte{0x4, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, }, }, wantErr: "TEE TCB security-version number [3 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0] is less than the required minimum [4 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0]", }, { name: "Test incorrect MR_SEAM", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ MrSeam: make([]byte, abi.MrSeamSize), }, }, wantErr: "quote field MR_SEAM", }, { name: "Test incorrect TD_ATTRIBUTES", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ TdAttributes: make([]byte, abi.TdAttributesSize), }, }, wantErr: "quote field TD_ATTRIBUTES", }, { name: "Test incorrect XFAM", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ Xfam: make([]byte, abi.XfamSize), }, }, wantErr: "quote field XFAM", }, { name: "Test incorrect MR_TD", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ MrTd: make([]byte, abi.MrTdSize), }, }, wantErr: "quote field MR_TD", }, { name: "Test incorrect MR_CONFIG_ID", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ MrConfigID: convert(make([]byte, abi.MrConfigIDSize), 1), }, }, wantErr: "quote field MR_CONFIG_ID", }, { name: "Test incorrect MR_OWNER", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ MrOwner: convert(make([]byte, abi.MrOwnerSize), 1), }, }, wantErr: "quote field MR_OWNER", }, { name: "Test incorrect MR_OWNER_CONFIG", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ MrOwnerConfig: convert(make([]byte, abi.MrOwnerConfigSize), 1), }, }, wantErr: "quote field MR_OWNER_CONFIG", }, {name: "Test incorrect RTMRS", quote: quote12345, opts: &Options{TdQuoteBodyOptions: TdQuoteBodyOptions{ Rtmrs: make2darray(abi.RtmrSize), }, }, wantErr: "quote field RTMR", }, { name: "Test incorrect REPORT_DATA", quote: quote12345, opts: &Options{ TdQuoteBodyOptions: TdQuoteBodyOptions{ ReportData: make([]byte, abi.ReportDataSize), }, }, wantErr: "quote field REPORT_DATA", }, { name: "Test incorrect QE_VENDOR_ID", quote: quote12345, opts: &Options{ HeaderOptions: HeaderOptions{QeVendorID: make([]byte, abi.QeVendorIDSize)}, }, wantErr: "quote field QE_VENDOR_ID"}, } for _, tc := range tests { if err := TdxQuote(tc.quote, tc.opts); (err == nil && tc.wantErr != "") || (err != nil && (tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr))) { t.Errorf("%s: TdxQuote() errored unexpectedly. Got '%v', want '%s'", tc.name, err, tc.wantErr) } } } go-tdx-guest-0.3.1/verify/000077500000000000000000000000001456371376300153655ustar00rootroot00000000000000go-tdx-guest-0.3.1/verify/trust/000077500000000000000000000000001456371376300165465ustar00rootroot00000000000000go-tdx-guest-0.3.1/verify/trust/trust.go000066400000000000000000000061071456371376300202620ustar00rootroot00000000000000// 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 trust defines core trust types and values for attestation verification. package trust import ( "context" "fmt" "io" "net/http" "time" ) // HTTPSGetter represents the ability to fetch data from the internet from an HTTP URL. type HTTPSGetter interface { Get(url string) (map[string][]string, []byte, error) } // AttestationRecreationErr represents a problem with fetching or interpreting associated // API responses for a given API call. 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) (map[string][]string, []byte, error) { var header map[string][]string resp, err := http.Get(url) if err != nil { return nil, nil, err } else if resp.StatusCode >= 300 { return nil, nil, fmt.Errorf("failed to retrieve %s, status code received %d", url, resp.StatusCode) } header = resp.Header body, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, err } resp.Body.Close() return header, 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) (map[string][]string, []byte, error) { delay := 2 * time.Second ctx, cancel := context.WithTimeout(context.Background(), n.Timeout) for { header, body, err := n.Getter.Get(url) if err == nil { cancel() return header, body, nil } delay = delay + delay if delay > n.MaxRetryDelay { delay = n.MaxRetryDelay } select { case <-ctx.Done(): cancel() return nil, nil, fmt.Errorf("timeout") // context cancelled case <-time.After(delay): // wait to retry } } } // DefaultHTTPSGetter returns the library's default getter implementation. It will // retry slowly. func DefaultHTTPSGetter() HTTPSGetter { return &RetryHTTPSGetter{ Timeout: 2 * time.Minute, MaxRetryDelay: 30 * time.Second, Getter: &SimpleHTTPSGetter{}, } } go-tdx-guest-0.3.1/verify/trusted_root.pem000066400000000000000000000016631456371376300206330ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= -----END CERTIFICATE-----go-tdx-guest-0.3.1/verify/verify.go000066400000000000000000001634511456371376300172320ustar00rootroot00000000000000// 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 verify provides the library functions to verify a TDX quote package verify import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/sha256" "crypto/x509" _ "embed" "encoding/binary" "encoding/hex" "encoding/json" "encoding/pem" "errors" "fmt" "math/big" "net/url" "os" "reflect" "time" "github.com/google/go-tdx-guest/abi" "github.com/google/go-tdx-guest/pcs" ccpb "github.com/google/go-tdx-guest/proto/checkconfig" pb "github.com/google/go-tdx-guest/proto/tdx" "github.com/google/go-tdx-guest/verify/trust" "github.com/google/logger" "go.uber.org/multierr" ) var ( // Embedded certificate used when trusted root is nil //go:embed trusted_root.pem defaultRootCertByte []byte trustedRootCertificate *x509.Certificate // ErrOptionsNil error returned when options parameter is empty ErrOptionsNil = errors.New("options parameter is empty") // ErrPCKCertChainNil error returned when PCK certificate chain field is empty in quote ErrPCKCertChainNil = errors.New("PCK certificate chain is empty") // ErrPCKCertChainInvalid error returned when PCK certificate chain has incomplete certificates ErrPCKCertChainInvalid = errors.New("incomplete PCK Certificate chain found, should contain 3 concatenated PEM-formatted 'CERTIFICATE'-type block (PCK Leaf Cert||Intermediate CA Cert||Root CA Cert)") // ErrRootCertNil error returned when Root CA certificate is empty ErrRootCertNil = errors.New("root certificate is empty") // ErrPCKCertNil error returned when PCK leaf certificate is empty ErrPCKCertNil = errors.New("PCK certificate is empty") // ErrIntermediateCertNil error returned when Intermediate CA certificate is empty ErrIntermediateCertNil = errors.New("intermediate certificate is empty") // ErrCertPubKeyType error returned when certificate public key is not of type ecdsa ErrCertPubKeyType = errors.New("certificate public key is not of type ecdsa public key") // ErrPublicKeySize error returned when public key bytes are of unexpected size ErrPublicKeySize = errors.New("public key is of unexpected size") // ErrKeyMismatch error returned when trusted public key is different from root CA certificate's public key ErrKeyMismatch = errors.New("root certificate's public key does not match with trusted public key") // ErrHashVerificationFail error returned when message digest verification failed using quote's ErrHashVerificationFail = errors.New("unable to verify message digest using quote's signature and ecdsa attestation key") // ErrSHA56VerificationFail error returned when sha256 verification fails ErrSHA56VerificationFail = errors.New("QE Report Data does not match with value of SHA 256 calculated over the concatenation of ECDSA Attestation Key and QE Authenticated Data") // ErrPckCertCANil error returned when CA is missing in PCK Certificate ErrPckCertCANil = errors.New("could not find CA from PCK certificate") // ErrEmptyRootCRLUrl error returned when QE identity issuer's chain root certificate has empty CRL distribution points ErrEmptyRootCRLUrl = errors.New("empty url found in QeIdentity issuer's chain which is required to receive ROOT CA CRL") // ErrCollateralNil error returned when collaterals are missing ErrCollateralNil = errors.New("collateral received is an empty structure") // ErrMissingPckCrl error returned when PCK CRL is missing ErrMissingPckCrl = errors.New("missing PCK CRL in the collaterals obtained") // ErrMissingRootCaCrl error returned when Root CA CRL CRL is missing ErrMissingRootCaCrl = errors.New("missing ROOT CA CRL in the collaterals obtained") // ErrMissingTcbInfoBody error returned when TCB info body is missing ErrMissingTcbInfoBody = errors.New("missing tcbInfo body in the collaterals obtained") // ErrMissingEnclaveIdentityBody error returned when Enclave Identity body is missing ErrMissingEnclaveIdentityBody = errors.New("missing enclaveIdentity body in the collaterals obtained") // ErrTcbInfoNil error returned when tcbInfo response structure is missing ErrTcbInfoNil = errors.New("tcbInfo is empty in collaterals") // ErrQeIdentityNil error returned when QeIdentity response structure is missing ErrQeIdentityNil = errors.New("QeIdentity is empty in collaterals") // ErrMissingPCKCrlSigningCert error returned when signing certificate is missing in issuer chain of PCK CRL ErrMissingPCKCrlSigningCert = errors.New("missing signing certificate in the issuer chain of PCK CRL") // ErrMissingPCKCrlRootCert error returned when root certificate is missing in issuer chain of PCK CRL ErrMissingPCKCrlRootCert = errors.New("missing root certificate in the issuer chain of PCK CRL") // ErrMissingTcbInfoSigningCert error returned when signing certificate is missing in issuer chain of tcbInfo ErrMissingTcbInfoSigningCert = errors.New("missing signing certificate in the issuer chain of tcbInfo") // ErrMissingTcbInfoRootCert error returned when root certificate is missing in issuer chain of tcbInfo ErrMissingTcbInfoRootCert = errors.New("missing root certificate in the issuer chain of tcbInfo") // ErrMissingQeIdentitySigningCert error returned when signing certificate is missing in issuer chain of QeIdentity ErrMissingQeIdentitySigningCert = errors.New("missing signing certificate in the issuer chain of QeIdentity") // ErrMissingQeIdentityRootCert error returned when root certificate is missing in issuer chain of QeIdentity ErrMissingQeIdentityRootCert = errors.New("missing root certificate in the issuer chain of QeIdentity") // ErrRootCaCrlExpired error returned when Root CA CRL is expired ErrRootCaCrlExpired = errors.New("root CA CRL has expired") // ErrPCKCrlExpired error returned when PCK CRL is expired ErrPCKCrlExpired = errors.New("PCK CRL has expired") // ErrTcbInfoExpired error returned when tcbInfo response is expired ErrTcbInfoExpired = errors.New("tcbInfo has expired") // ErrQeIdentityExpired error returned when QeIdentity response is expired ErrQeIdentityExpired = errors.New("QeIdentity has expired") // ErrPCKCrlSigningCertExpired error returned when PCK CRL signing certificate is expired ErrPCKCrlSigningCertExpired = errors.New("PCK CRL signing certificate has expired") // ErrPCKCrlRootCertExpired error returned when PCK CRL root certificate is expired ErrPCKCrlRootCertExpired = errors.New("PCK CRL root certificate has expired") // ErrTcbInfoSigningCertExpired error returned when tcbInfo signing certificate is expired ErrTcbInfoSigningCertExpired = errors.New("tcbInfo signing certificate has expired") // ErrTcbInfoRootCertExpired error returned when tcbInfo root certificate is expired ErrTcbInfoRootCertExpired = errors.New("tcbInfo root certificate has expired") // ErrQeIdentitySigningCertExpired error returned when QeIdentity signing certificate is expired ErrQeIdentitySigningCertExpired = errors.New("QeIdentity signing certificate has expired") // ErrQeIdentityRootCertExpired error returned when QeIdentity root certificate is expired ErrQeIdentityRootCertExpired = errors.New("QeIdentity root certificate has expired") // ErrCrlEmpty error returned when Certificate Revocation list is empty ErrCrlEmpty = errors.New("CRL is empty") // ErrTrustedCertEmpty error returned when no trusted certificate is provided for verification ErrTrustedCertEmpty = errors.New("trusted certificate is empty") // ErrRevocationCheckFailed error returned when CheckRevocations parameter is set to true and GetCollateral is set to false ErrRevocationCheckFailed = errors.New("unable to check for certificate revocation as GetCollateral parameter in the options is set to false") // ErrTcbInfoTcbLevelsMissing error returned when TCBLevels array in TCB info is of length 0 ErrTcbInfoTcbLevelsMissing = errors.New("tcbInfo contains empty TcbLevels") // ErrQeIdentityTcbLevelsMissing error returned when TCBLevels array in QE Identity is of length 0 ErrQeIdentityTcbLevelsMissing = errors.New("QeIdentity contains empty TcbLevels") // ErrPckLeafCertExpired error returned when PCK Leaf certificate has expired ErrPckLeafCertExpired = errors.New("PCK leaf certificate in PCK certificate chain has expired") // ErrRootCaCertExpired error returned when Root CA certificate has expired ErrRootCaCertExpired = errors.New("root CA certificate in PCK certificate chain has expired") // ErrIntermediateCaCertExpired error returned when Intermediate CA certificate has expired ErrIntermediateCaCertExpired = errors.New("intermediate CA certificate in PCK certificate chain has expired") // ErrTcbStatus error returned when TCB status is out of date ErrTcbStatus = errors.New("unable to find latest status of TCB, it is now OutOfDate") // ErrCertNil error returned when certificate is not provided ErrCertNil = errors.New("certificate is nil") // ErrParentCertNil error returned when parent certificate is not provided ErrParentCertNil = errors.New("parent certificate is nil") ) const ( pubKeySize = 64 pubKeyHeaderByte = 0x04 tcbInfoVersion = 3.0 qeIdentityVersion = 2.0 rootCertPhrase = "Intel SGX Root CA" intermediateCertPhrase = "Intel SGX PCK Platform CA" pckCertPhrase = "Intel SGX PCK Certificate" processorIssuer = "Intel SGX PCK Processor CA" processorIssuerID = "processor" platformIssuer = "Intel SGX PCK Platform CA" platformIssuerID = "platform" sgxPckCrlIssuerChainPhrase = "Sgx-Pck-Crl-Issuer-Chain" sgxQeIdentityIssuerChainPhrase = "Sgx-Enclave-Identity-Issuer-Chain" tcbInfoIssuerChainPhrase = "Tcb-Info-Issuer-Chain" tcbInfoPhrase = "tcbInfo" enclaveIdentityPhrase = "enclaveIdentity" certificateType = "CERTIFICATE" tcbInfoID = "TDX" qeIdentityID = "TD_QE" tcbSigningPhrase = "Intel SGX TCB Signing" tcbInfoTdxModuleIDPrefix = "TDX_" ) // Options represents verification options for a TDX attestation quote. type Options struct { // CheckRevocations set to true if the verifier should retrieve the CRL from the network and check // if the PCK certificate chain have been revoked. CheckRevocations bool // GetCollateral set to true if the verifier should retrieve the collaterals from the network using PCS. GetCollateral bool // Getter takes a URL and returns the body of its contents. By default uses http.Get and returns the header and body Getter trust.HTTPSGetter // Now is the time at which to verify the validity of certificates and collaterals. If unset, uses time.Now(). Now time.Time // TrustedRoots specifies the root CertPool to trust when verifying PCK certificate chain. // If nil, embedded certificate will be used TrustedRoots *x509.CertPool chain *PCKCertificateChain collateral *Collateral pckCertExtensions *pcs.PckExtensions } // DefaultOptions returns a useful default verification option setting func DefaultOptions() *Options { return &Options{ Getter: trust.DefaultHTTPSGetter(), Now: time.Now(), } } type tdQuoteBodyOptions struct { tcbInfo pcs.TcbInfo pckCertExtensions *pcs.PckExtensions } type qeReportOptions struct { qeIdentity *pcs.EnclaveIdentity } // PCKCertificateChain contains certificate chains type PCKCertificateChain struct { PCKCertificate *x509.Certificate // PCK Leaf certificate RootCertificate *x509.Certificate // Root CA certificate IntermediateCertificate *x509.Certificate // Intermediate CA certificate } // Collateral contains information received from Intel PCS API service type Collateral struct { PckCrlIssuerIntermediateCertificate *x509.Certificate PckCrlIssuerRootCertificate *x509.Certificate PckCrl *x509.RevocationList TcbInfoIssuerIntermediateCertificate *x509.Certificate TcbInfoIssuerRootCertificate *x509.Certificate TdxTcbInfo pcs.TdxTcbInfo TcbInfoBody []byte QeIdentityIssuerIntermediateCertificate *x509.Certificate QeIdentityIssuerRootCertificate *x509.Certificate QeIdentity pcs.QeIdentity EnclaveIdentityBody []byte RootCaCrl *x509.RevocationList } // 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 } func applyMask(a, b []byte) []byte { data := make([]byte, len(a)) for i := 0; i < len(a); i++ { data[i] = a[i] & b[i] } return data } func extractCaFromPckCert(pckCert *x509.Certificate) (string, error) { pckIssuer := pckCert.Issuer.CommonName if pckIssuer == platformIssuer { return platformIssuerID, nil } if pckIssuer == processorIssuer { return processorIssuerID, nil } return "", ErrPckCertCANil } // bytesToEcdsaPubKey converts byte array to ecdsa public key format func bytesToEcdsaPubKey(b []byte) (*ecdsa.PublicKey, error) { size := len(b) if size != pubKeySize { return nil, ErrPublicKeySize } publicKey := &ecdsa.PublicKey{ Curve: elliptic.P256(), X: new(big.Int).SetBytes(b[0 : pubKeySize/2]), Y: new(big.Int).SetBytes(b[pubKeySize/2 : pubKeySize]), } logger.V(2).Info("Public Key is on curve ", publicKey.Curve.Params().Name) if !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { return nil, fmt.Errorf("public key is not on curve %q", publicKey.Curve.Params().Name) } return publicKey, nil } func headerToIssuerChain(header map[string][]string, phrase string) (*x509.Certificate, *x509.Certificate, error) { issuerChain, ok := header[phrase] if !ok { return nil, nil, fmt.Errorf("%q is empty", phrase) } if len(issuerChain) != 1 { return nil, nil, fmt.Errorf("issuer chain is expected to be of size 1, found %d", len(issuerChain)) } if issuerChain[0] == "" { return nil, nil, fmt.Errorf("issuer chain certificates missing in %q", phrase) } certChain, err := url.QueryUnescape(issuerChain[0]) if err != nil { return nil, nil, fmt.Errorf("unable to decode issuer chain in %q: %v", phrase, err) } intermediate, rem := pem.Decode([]byte(certChain)) if intermediate == nil || len(rem) == 0 { return nil, nil, fmt.Errorf("could not parse PEM formatted signing certificate in %q", phrase) } if intermediate.Type != certificateType { return nil, nil, fmt.Errorf(`the %q PEM block type is %q. Expect %q`, phrase, intermediate.Type, certificateType) } intermediateCert, err := x509.ParseCertificate(intermediate.Bytes) if err != nil { return nil, nil, fmt.Errorf("could not interpret DER bytes of signing certificate in %q: %v", phrase, err) } root, rem := pem.Decode(rem) if root == nil || len(rem) != 0 { return nil, nil, fmt.Errorf("could not parse PEM formatted root certificate in %q", phrase) } if root.Type != certificateType { return nil, nil, fmt.Errorf(`the %q PEM block type is %q. Expect %q`, phrase, root.Type, certificateType) } rootCert, err := x509.ParseCertificate(root.Bytes) if err != nil { return nil, nil, fmt.Errorf("could not interpret DER bytes of root certificate in %q: %v", phrase, err) } return intermediateCert, rootCert, nil } func bodyToCrl(body []byte) (*x509.RevocationList, error) { crl, err := x509.ParseRevocationList(body) if err != nil { return nil, fmt.Errorf("unable to parse DER bytes of CRL: %v", err) } return crl, nil } func bodyToRawMessage(name string, body []byte) ([]byte, error) { if len(body) == 0 { return nil, fmt.Errorf("%q is empty", name) } var rawbody map[string]json.RawMessage if err := json.Unmarshal(body, &rawbody); err != nil { return nil, fmt.Errorf("could not convert %q body to raw message: %v", name, err) } val, ok := rawbody[name] if !ok { return nil, fmt.Errorf("%q field is missing in the response received", name) } return val, nil } func getPckCrl(ca string, getter trust.HTTPSGetter, collateral *Collateral) error { pckCrlURL := pcs.PckCrlURL(ca) logger.V(2).Info("Getting PCK CRL: ", pckCrlURL) header, body, err := getter.Get(pckCrlURL) if err != nil { return CRLUnavailableErr{multierr.Append(err, errors.New("could not fetch PCK CRL"))} } pckCrlIntermediateCert, pckCrlRootCert, err := headerToIssuerChain(header, sgxPckCrlIssuerChainPhrase) if err != nil { return err } collateral.PckCrlIssuerIntermediateCertificate = pckCrlIntermediateCert collateral.PckCrlIssuerRootCertificate = pckCrlRootCert crl, err := bodyToCrl(body) if err != nil { return CRLUnavailableErr{multierr.Append(err, errors.New("could not fetch PCK CRL"))} } collateral.PckCrl = crl return nil } func getTcbInfo(fmspc string, getter trust.HTTPSGetter, collateral *Collateral) error { tcbInfoURL := pcs.TcbInfoURL(fmspc) logger.V(2).Info("Getting TCB Info: ", tcbInfoURL) header, body, err := getter.Get(tcbInfoURL) if err != nil { return &trust.AttestationRecreationErr{ Msg: fmt.Sprintf("could not receive tcbInfo response: %v", err), } } tcbInfoIntermediateCert, tcbInfoRootCert, err := headerToIssuerChain(header, tcbInfoIssuerChainPhrase) if err != nil { return &trust.AttestationRecreationErr{ Msg: err.Error(), } } collateral.TcbInfoIssuerIntermediateCertificate = tcbInfoIntermediateCert collateral.TcbInfoIssuerRootCertificate = tcbInfoRootCert if err := json.Unmarshal(body, &collateral.TdxTcbInfo); err != nil { return &trust.AttestationRecreationErr{ Msg: fmt.Sprintf("unable to unmarshal tcbInfo response: %v", err), } } tcbInfoRawBody, err := bodyToRawMessage(tcbInfoPhrase, body) if err != nil { return &trust.AttestationRecreationErr{ Msg: err.Error(), } } collateral.TcbInfoBody = tcbInfoRawBody return nil } func getQeIdentity(getter trust.HTTPSGetter, collateral *Collateral) error { qeIdentityURL := pcs.QeIdentityURL() logger.V(2).Info("Getting QE Identity: ", qeIdentityURL) header, body, err := getter.Get(qeIdentityURL) if err != nil { return &trust.AttestationRecreationErr{ Msg: fmt.Sprintf("could not receive QeIdentity response: %v", err), } } qeIdentityIntermediateCert, qeIdentityRootCert, err := headerToIssuerChain(header, sgxQeIdentityIssuerChainPhrase) if err != nil { return &trust.AttestationRecreationErr{ Msg: err.Error(), } } collateral.QeIdentityIssuerIntermediateCertificate = qeIdentityIntermediateCert collateral.QeIdentityIssuerRootCertificate = qeIdentityRootCert if err := json.Unmarshal(body, &collateral.QeIdentity); err != nil { return &trust.AttestationRecreationErr{ Msg: fmt.Sprintf("unable to unmarshal QeIdentity response: %v", err), } } qeIdentityRawBody, err := bodyToRawMessage(enclaveIdentityPhrase, body) if err != nil { return &trust.AttestationRecreationErr{ Msg: err.Error(), } } collateral.EnclaveIdentityBody = qeIdentityRawBody return nil } func getRootCrl(getter trust.HTTPSGetter, collateral *Collateral) error { rootCrlURL := collateral.QeIdentityIssuerRootCertificate.CRLDistributionPoints // QE identity issuer chain's root certificate contains URL for Root CA CRL if len(rootCrlURL) == 0 { return ErrEmptyRootCRLUrl } logger.V(2).Info("Getting Root CA CRL: ", rootCrlURL) var errs error for i := range rootCrlURL { _, body, err := getter.Get(rootCrlURL[i]) if err != nil { errs = multierr.Append(errs, err) continue } crl, err := bodyToCrl(body) if err != nil { errs = multierr.Append(errs, err) continue } collateral.RootCaCrl = crl return nil } return CRLUnavailableErr{multierr.Append(errs, errors.New("could not fetch root CRL"))} } func obtainCollateral(fmspc string, ca string, options *Options) (*Collateral, error) { getter := options.Getter if getter == nil { getter = trust.DefaultHTTPSGetter() } collateral := &Collateral{} logger.V(1).Info("Getting TCB Info API response from the Intel PCS") if err := getTcbInfo(fmspc, getter, collateral); err != nil { return nil, fmt.Errorf("unable to receive tcbInfo: %v", err) } logger.V(1).Info("Successfully received TCB Info API response from the Intel PCS") logger.V(1).Info("Getting QE Identity API response from the Intel PCS") if err := getQeIdentity(getter, collateral); err != nil { return nil, fmt.Errorf("unable to receive QeIdentity: %v", err) } logger.V(1).Info("Successfully received QE Identity API response from the Intel PCS") if options.CheckRevocations { logger.V(1).Info("Getting PCK CRL from the Intel PCS") if err := getPckCrl(ca, getter, collateral); err != nil { return nil, fmt.Errorf("unable to receive PCK CRL: %v", err) } logger.V(1).Info("Successfully received PCK CRL from the Intel PCS") logger.V(1).Info("Getting Root CA CRL from the Intel PCS") if err := getRootCrl(getter, collateral); err != nil { return nil, fmt.Errorf("unable to receive Root CA CRL: %v", err) } logger.V(1).Info("Successfully received Root CA CRL from the Intel PCS") } return collateral, nil } func checkCollateralExpiration(collateral *Collateral, options *Options) error { currentTime := options.Now logger.V(1).Info("Checking expiration status of collaterals") tcbInfo := collateral.TdxTcbInfo.TcbInfo qeIdentity := collateral.QeIdentity.EnclaveIdentity if currentTime.After(tcbInfo.NextUpdate) { return ErrTcbInfoExpired } if currentTime.After(qeIdentity.NextUpdate) { return ErrQeIdentityExpired } if currentTime.After(collateral.TcbInfoIssuerIntermediateCertificate.NotAfter) { return ErrTcbInfoSigningCertExpired } if currentTime.After(collateral.TcbInfoIssuerRootCertificate.NotAfter) { return ErrTcbInfoRootCertExpired } if currentTime.After(collateral.QeIdentityIssuerRootCertificate.NotAfter) { return ErrQeIdentityRootCertExpired } if currentTime.After(collateral.QeIdentityIssuerIntermediateCertificate.NotAfter) { return ErrQeIdentitySigningCertExpired } if options.CheckRevocations { if currentTime.After(collateral.RootCaCrl.NextUpdate) { return ErrRootCaCrlExpired } if currentTime.After(collateral.PckCrl.NextUpdate) { return ErrPCKCrlExpired } if currentTime.After(collateral.PckCrlIssuerIntermediateCertificate.NotAfter) { return ErrPCKCrlSigningCertExpired } if currentTime.After(collateral.PckCrlIssuerRootCertificate.NotAfter) { return ErrPCKCrlRootCertExpired } } logger.V(1).Info("Collaterals not expired") return nil } func checkCertificateExpiration(chain *PCKCertificateChain, options *Options) error { currentTime := options.Now logger.V(1).Info("Checking expiration status of certificates") if currentTime.After(chain.RootCertificate.NotAfter) { return ErrRootCaCertExpired } if currentTime.After(chain.IntermediateCertificate.NotAfter) { return ErrIntermediateCaCertExpired } if currentTime.After(chain.PCKCertificate.NotAfter) { return ErrPckLeafCertExpired } logger.V(1).Info("Certificates are up-to-date") return nil } func verifyCollateral(options *Options) error { collateral := options.collateral if collateral == nil { return ErrCollateralNil } if collateral.TcbInfoBody == nil { return ErrMissingTcbInfoBody } if collateral.EnclaveIdentityBody == nil { return ErrMissingEnclaveIdentityBody } if reflect.DeepEqual(collateral.TdxTcbInfo, pcs.TdxTcbInfo{}) { return ErrTcbInfoNil } if reflect.DeepEqual(collateral.QeIdentity, pcs.QeIdentity{}) { return ErrQeIdentityNil } if collateral.TcbInfoIssuerIntermediateCertificate == nil { return ErrMissingTcbInfoSigningCert } if collateral.TcbInfoIssuerRootCertificate == nil { return ErrMissingTcbInfoRootCert } if collateral.QeIdentityIssuerIntermediateCertificate == nil { return ErrMissingQeIdentitySigningCert } if collateral.QeIdentityIssuerRootCertificate == nil { return ErrMissingQeIdentityRootCert } if options.CheckRevocations { if collateral.PckCrl == nil { return ErrMissingPckCrl } if collateral.RootCaCrl == nil { return ErrMissingRootCaCrl } if collateral.PckCrlIssuerIntermediateCertificate == nil { return ErrMissingPCKCrlSigningCert } if collateral.PckCrlIssuerRootCertificate == nil { return ErrMissingPCKCrlRootCert } } return checkCollateralExpiration(collateral, options) } func extractChainFromQuote(quote any) (*PCKCertificateChain, error) { switch q := quote.(type) { case *pb.QuoteV4: return extractChainFromQuoteV4(q) default: return nil, fmt.Errorf("unsupported quote type: %T", quote) } } func extractChainFromQuoteV4(quote *pb.QuoteV4) (*PCKCertificateChain, error) { certChainBytes := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData().GetPckCertificateChainData().GetPckCertChain() if certChainBytes == nil { return nil, ErrPCKCertChainNil } pck, rem := pem.Decode(certChainBytes) if pck == nil || len(rem) == 0 || pck.Type != certificateType { return nil, ErrPCKCertChainInvalid } pckCert, err := x509.ParseCertificate(pck.Bytes) if err != nil { return nil, fmt.Errorf("could not interpret PCK leaf certificate DER bytes: %v", err) } logger.V(1).Info("PCK Leaf certificate has been extracted from the certificate chain") intermediate, rem := pem.Decode(rem) if intermediate == nil || len(rem) == 0 || intermediate.Type != certificateType { return nil, ErrPCKCertChainInvalid } intermediateCert, err := x509.ParseCertificate(intermediate.Bytes) if err != nil { return nil, fmt.Errorf("could not interpret Intermediate CA certificate DER bytes: %v", err) } logger.V(1).Info("Intermediate certificate has been extracted from the certificate chain") root, rem := pem.Decode(rem) if root == nil || root.Type != certificateType { return nil, ErrPCKCertChainInvalid } // The final byte of the certificate chain can be a null byte. if len(rem) != 0 && !bytes.Equal(rem, []byte{0x00}) { return nil, fmt.Errorf("unexpected trailing bytes were found in PCK Certificate Chain: %d byte(s)", len(rem)) } rootCert, err := x509.ParseCertificate(root.Bytes) if err != nil { return nil, fmt.Errorf("could not interpret Root CA certificate DER bytes: %v", err) } logger.V(1).Info("Root CA certificate has been extracted from the certificate chain") return &PCKCertificateChain{PCKCertificate: pckCert, RootCertificate: rootCert, IntermediateCertificate: intermediateCert}, nil } func validateX509Cert(cert *x509.Certificate, version int, signatureAlgorithm x509.SignatureAlgorithm, publicKeyAlgorithm x509.PublicKeyAlgorithm, curve string) error { logger.V(2).Info("Certificate version: ", cert.Version) if cert.Version != version { return fmt.Errorf("certificate's version found %v. Expected %d", cert.Version, version) } logger.V(2).Info("Certificate's signature algorithm: ", cert.SignatureAlgorithm) if cert.SignatureAlgorithm != signatureAlgorithm { return fmt.Errorf("certificate's signature algorithm found %v. Expected %v", cert.SignatureAlgorithm, signatureAlgorithm) } logger.V(2).Info("Certificate's public key algorithm: ", cert.PublicKeyAlgorithm) if cert.PublicKeyAlgorithm != publicKeyAlgorithm { return fmt.Errorf("certificate's public Key algorithm found %v. Expected %v", cert.PublicKeyAlgorithm, 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: logger.V(2).Info("Certificate's public key curve: ", pub.Curve.Params().Name) if pub.Curve.Params().Name != curve { return fmt.Errorf("certificate's public key curve is %q. Expected %q", pub.Curve.Params().Name, curve) } default: return ErrCertPubKeyType } return nil } func validateCertificate(cert *x509.Certificate, parent *x509.Certificate, phrase string) error { if cert == nil { return ErrCertNil } if parent == nil { return ErrParentCertNil } // Signature algorithm: ECDSA // Signature hash algorithm sha256 // Subject Public Key Info ECDSA on curve P-256 if err := validateX509Cert(cert, 3, x509.ECDSAWithSHA256, x509.ECDSA, "P-256"); err != nil { return err } logger.V(2).Info("Certificate's subject name found: ", cert.Subject.CommonName) if cert.Subject.CommonName != phrase { return fmt.Errorf("%q is not expected in certificate's subject name. Expected %q", cert.Subject.CommonName, phrase) } logger.V(2).Info("Certificate's issuer name found: ", cert.Issuer.String()) if cert.Issuer.String() != parent.Subject.String() { return fmt.Errorf("certificate's issuer name (%q), does not match with parent certificate's subject name (%q)", cert.Issuer.String(), parent.Subject.String()) } if err := cert.CheckSignatureFrom(parent); err != nil { return fmt.Errorf("certificate signature verification using parent certificate failed: %v", err) } logger.V(1).Info("Certificate's signature verified using parent certificate") return nil } func validateCRL(crl *x509.RevocationList, trustedCertificate *x509.Certificate) error { if crl == nil { return ErrCrlEmpty } if trustedCertificate == nil { return ErrTrustedCertEmpty } logger.V(2).Info("CRL issuer's name found: ", crl.Issuer.String()) if crl.Issuer.String() != trustedCertificate.Subject.String() { return fmt.Errorf("CRL issuer's name %q does not match with expected name %q", crl.Issuer.String(), trustedCertificate.Subject.String()) } if err := crl.CheckSignatureFrom(trustedCertificate); err != nil { return fmt.Errorf("CRL signature verification failed using trusted certificate: %v", err) } logger.V(1).Info("CRL signature verified using trusted certificate") return nil } func getHeaderAndTdQuoteBodyInAbiBytes(quote *pb.QuoteV4) ([]byte, error) { header, err := abi.HeaderToAbiBytes(quote.GetHeader()) if err != nil { return nil, fmt.Errorf("could not convert header to ABI bytes: %v", err) } tdQuoteBody, err := abi.TdQuoteBodyToAbiBytes(quote.GetTdQuoteBody()) if err != nil { return nil, fmt.Errorf("could not convert TD Quote Body to ABI bytes: %v", err) } return append(header, tdQuoteBody...), nil } func verifyPCKCertificationChain(options *Options) error { chain := options.chain collateral := options.collateral rootCert := chain.RootCertificate if rootCert == nil { return ErrRootCertNil } intermediateCert := chain.IntermediateCertificate if intermediateCert == nil { return ErrIntermediateCertNil } pckCert := chain.PCKCertificate if pckCert == nil { return ErrPCKCertNil } logger.V(1).Info("Verifying the Root CA Certificate") // root certificate should be a self-signed certificate if err := validateCertificate(rootCert, rootCert, rootCertPhrase); err != nil { return fmt.Errorf("unable to validate root cert: %v", err) } logger.V(1).Info("Root CA Certificate verified successfully") logger.V(1).Info("Verifying Intermediate CA certificate") if err := validateCertificate(intermediateCert, rootCert, intermediateCertPhrase); err != nil { return fmt.Errorf("unable to validate Intermediate CA certificate: %v", err) } logger.V(1).Info("Intermediate CA Certificate verified successfully") logger.V(1).Info("Verifying PCK Leaf certificate") if err := validateCertificate(pckCert, intermediateCert, pckCertPhrase); err != nil { return fmt.Errorf("unable to validate PCK leaf certificate: %v", err) } logger.V(1).Info("PCK Leaf Certificate verified successfully") if _, err := pckCert.Verify(x509Options(options.TrustedRoots, intermediateCert, options.Now)); err != nil { return fmt.Errorf("error verifying PCK Certificate: %v (%v)", err, rootCert.IsCA) } logger.V(1).Info("PCK Certificate Chain verified successfully") if options.CheckRevocations { if options.GetCollateral { logger.V(1).Info("Verifying Root CA CRL") if err := validateCRL(collateral.RootCaCrl, rootCert); err != nil { return fmt.Errorf("root CA CRL verification failed using root certificate in PCK Certificate chain: %v", err) } logger.V(1).Info("Root CA CRL verified successfully") logger.V(1).Info("Verifying PCK CRL") if err := validateCRL(collateral.PckCrl, intermediateCert); err != nil { return fmt.Errorf("PCK CRL verification failed using intermediate certificate in PCK Certificate chain: %v", err) } logger.V(1).Info("PCK CRL verified successfully") if collateral.PckCrl.Issuer.String() != pckCert.Issuer.String() { return fmt.Errorf("issuer's name(%q) in PCK CRL does not match with PCK Leaf Certificate's issuer name(%q)", collateral.PckCrl.Issuer.String(), pckCert.Issuer.String()) } for _, bad := range collateral.RootCaCrl.RevokedCertificates { if intermediateCert.SerialNumber.Cmp(bad.SerialNumber) == 0 { return fmt.Errorf("intermediate certificate in PCK certificate chain was revoked at %v", bad.RevocationTime) } } logger.V(1).Info("Intermediate Certificate is not revoked by Root CA CRL") for _, bad := range collateral.PckCrl.RevokedCertificates { if pckCert.SerialNumber.Cmp(bad.SerialNumber) == 0 { return fmt.Errorf("PCK Leaf certificate in PCK certificate chain was revoked at %v", bad.RevocationTime) } } logger.V(1).Info("PCK Leaf Certificate is not revoked by PCK CRL") } else { return ErrRevocationCheckFailed } } return checkCertificateExpiration(chain, options) } func x509Options(trustedRoots *x509.CertPool, intermediateCert *x509.Certificate, now time.Time) x509.VerifyOptions { if trustedRoots == nil { logger.Warning("Using embedded Intel certificate for TDX attestation root of trust") trustedRoots = x509.NewCertPool() trustedRoots.AddCert(trustedRootCertificate) } intermediates := x509.NewCertPool() if intermediateCert != nil { intermediates.AddCert(intermediateCert) } return x509.VerifyOptions{Roots: trustedRoots, Intermediates: intermediates, CurrentTime: now} } func verifyHash256(quote *pb.QuoteV4) error { qeReportCertificationData := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData() qeReportData := qeReportCertificationData.GetQeReport().GetReportData() qeAuthData := qeReportCertificationData.GetQeAuthData().GetData() attestKey := quote.GetSignedData().GetEcdsaAttestationKey() concatOfAttestKeyandQeAuthData := append(attestKey, qeAuthData...) var hashedMessage []byte hashedConcatOfAttestKeyandQeAuthData := sha256.Sum256(concatOfAttestKeyandQeAuthData) hashedMessage = hashedConcatOfAttestKeyandQeAuthData[:] hashedMessage = append(hashedMessage, make([]byte, len(qeReportData)-len(hashedMessage))...) if !bytes.Equal(hashedMessage, qeReportData) { return ErrSHA56VerificationFail } return nil } func isCPUSvnHigherOrEqual(pckCertCPUSvnComponents []byte, sgxTcbcomponents []pcs.TcbComponent) bool { if len(pckCertCPUSvnComponents) != len(sgxTcbcomponents) { return false } for i := range pckCertCPUSvnComponents { if pckCertCPUSvnComponents[i] < sgxTcbcomponents[i].Svn { return false } } return true } func isTdxTcbSvnHigherOrEqual(teeTcbSvn []byte, tdxTcbcomponents []pcs.TcbComponent) bool { if len(teeTcbSvn) != len(tdxTcbcomponents) { return false } start := 0 if teeTcbSvn[1] > 0 { start = 2 } for i := start; i < len(teeTcbSvn); i++ { if teeTcbSvn[i] < tdxTcbcomponents[i].Svn { return false } } return true } func getMatchingTdxModuleTcbLevel(tcbInfoTdxModuleIdentities []pcs.TdxModuleIdentity, teeTcbSvn []byte) (*pcs.TcbLevel, error) { tdxModuleVersion := []byte(teeTcbSvn[1:2]) tdxModuleIdentityID := tcbInfoTdxModuleIDPrefix + hex.EncodeToString(tdxModuleVersion) tdxModuleIsvSvn := uint32(teeTcbSvn[0]) for _, tdxModuleIdentity := range tcbInfoTdxModuleIdentities { if tdxModuleIdentityID == tdxModuleIdentity.ID { for _, tcbLevel := range tdxModuleIdentity.TcbLevels { if tdxModuleIsvSvn >= tcbLevel.Tcb.Isvsvn { logger.V(2).Infof("TDX Module Identity's TCB Level's ISVSVN(%q) matched the TDX Module's ISVSVN(%q)", tcbLevel.Tcb.Isvsvn, tdxModuleIsvSvn) return &tcbLevel, nil } } return nil, fmt.Errorf("could not find a TDX Module Identity TCB Level matching the TDX Module's ISVSVN (%d)", tdxModuleIsvSvn) } } return nil, fmt.Errorf("could not find a TDX Module Identity (%q) matching the given TEE TDX version (%q)", tdxModuleIdentityID, tdxModuleVersion) } func getMatchingTcbLevel(tcbLevels []pcs.TcbLevel, tdReport *pb.TDQuoteBody, pckCertPceSvn uint16, pckCertCPUSvnComponents []byte) (pcs.TcbLevel, error) { for _, tcbLevel := range tcbLevels { if isCPUSvnHigherOrEqual(pckCertCPUSvnComponents, tcbLevel.Tcb.SgxTcbcomponents) && pckCertPceSvn >= tcbLevel.Tcb.Pcesvn && isTdxTcbSvnHigherOrEqual(tdReport.GetTeeTcbSvn(), tcbLevel.Tcb.TdxTcbcomponents) { return tcbLevel, nil } } return pcs.TcbLevel{}, fmt.Errorf("no matching TCB level found") } func checkQeTcbStatus(tcbLevels []pcs.TcbLevel, isvsvn uint32) error { for _, tcbLevel := range tcbLevels { if tcbLevel.Tcb.Isvsvn <= isvsvn { if tcbLevel.TcbStatus != pcs.TcbComponentStatusUpToDate { return fmt.Errorf("TCB Status is not %q, found %q", pcs.TcbComponentStatusUpToDate, tcbLevel.TcbStatus) } return nil } } return ErrTcbStatus } func checkTcbInfoTcbStatus(tcbInfo pcs.TcbInfo, tdQuoteBody *pb.TDQuoteBody, pckCertExtensions *pcs.PckExtensions) error { tcbLevels := tcbInfo.TcbLevels matchingTcbLevel, err := getMatchingTcbLevel(tcbLevels, tdQuoteBody, pckCertExtensions.TCB.PCESvn, pckCertExtensions.TCB.CPUSvnComponents) if err != nil { return err } logger.V(2).Info("Matching TCB Level found: ", matchingTcbLevel) if tdQuoteBody.GetTeeTcbSvn()[1] > 0 { matchingTdxModuleTcbLevel, err := getMatchingTdxModuleTcbLevel(tcbInfo.TdxModuleIdentities, tdQuoteBody.GetTeeTcbSvn()) if err != nil { return err } logger.V(2).Info("Tdx Module TCB Status found: ", matchingTdxModuleTcbLevel.TcbStatus) if matchingTdxModuleTcbLevel.TcbStatus != pcs.TcbComponentStatusUpToDate { return fmt.Errorf("TDX Module TCB Status is not %q, found %q", pcs.TcbComponentStatusUpToDate, matchingTdxModuleTcbLevel.TcbStatus) } } logger.V(2).Info("TCB Status found: ", matchingTcbLevel.TcbStatus) if matchingTcbLevel.TcbStatus != pcs.TcbComponentStatusUpToDate { return fmt.Errorf("TCB Status is not %q, found %q", pcs.TcbComponentStatusUpToDate, matchingTcbLevel.TcbStatus) } return nil } func verifyTdQuoteBody(tdQuoteBody *pb.TDQuoteBody, tdQuoteBodyOptions *tdQuoteBodyOptions) error { logger.V(2).Infof("FMSPC from PCK Certificate is %q, and FMSPC value from Intel PCS's reported TDX TCB info is %q", tdQuoteBodyOptions.pckCertExtensions.FMSPC, tdQuoteBodyOptions.tcbInfo.Fmspc) if tdQuoteBodyOptions.pckCertExtensions.FMSPC != tdQuoteBodyOptions.tcbInfo.Fmspc { return fmt.Errorf("FMSPC from PCK Certificate(%q) is not equal to FMSPC value from Intel PCS's reported TDX TCB info(%q)", tdQuoteBodyOptions.pckCertExtensions.FMSPC, tdQuoteBodyOptions.tcbInfo.Fmspc) } logger.V(2).Infof("PCEID from PCK Certificate is %q, and PCEID from Intel PCS's reported TDX TCB info is %q", tdQuoteBodyOptions.pckCertExtensions.PCEID, tdQuoteBodyOptions.tcbInfo.PceID) if tdQuoteBodyOptions.pckCertExtensions.PCEID != tdQuoteBodyOptions.tcbInfo.PceID { return fmt.Errorf("PCEID from PCK Certificate(%q) is not equal to PCEID from Intel PCS's reported TDX TCB info(%q)", tdQuoteBodyOptions.pckCertExtensions.PCEID, tdQuoteBodyOptions.tcbInfo.PceID) } logger.V(2).Infof("MRSIGNERSEAM value from TD Quote Body is %q, and TdxModule.Mrsigner field in Intel PCS's reported TDX TCB info is %q", hex.EncodeToString(tdQuoteBody.GetMrSignerSeam()), hex.EncodeToString(tdQuoteBodyOptions.tcbInfo.TdxModule.Mrsigner.Bytes)) if !bytes.Equal(tdQuoteBodyOptions.tcbInfo.TdxModule.Mrsigner.Bytes, tdQuoteBody.GetMrSignerSeam()) { return fmt.Errorf("MRSIGNERSEAM value from TD Quote Body(%q) is not equal to TdxModule.Mrsigner field in Intel PCS's reported TDX TCB info(%q)", hex.EncodeToString(tdQuoteBody.GetMrSignerSeam()), hex.EncodeToString(tdQuoteBodyOptions.tcbInfo.TdxModule.Mrsigner.Bytes)) } if len(tdQuoteBodyOptions.tcbInfo.TdxModule.AttributesMask.Bytes) != len(tdQuoteBody.GetSeamAttributes()) { return fmt.Errorf("size of SeamAttributes from TD Quote Body(%d) is not equal to size of TdxModule.AttributesMask in Intel PCS's reported TDX TCB info(%d)", len(tdQuoteBodyOptions.tcbInfo.TdxModule.AttributesMask.Bytes), len(tdQuoteBody.GetSeamAttributes())) } attributesMask := applyMask(tdQuoteBodyOptions.tcbInfo.TdxModule.AttributesMask.Bytes, tdQuoteBody.GetSeamAttributes()) logger.V(2).Infof("AttributesMask value is %q, and TdxModule.Attributes field in Intel PCS's reported TDX TCB info is %q", hex.EncodeToString(attributesMask), hex.EncodeToString(tdQuoteBodyOptions.tcbInfo.TdxModule.Attributes.Bytes)) if !bytes.Equal(tdQuoteBodyOptions.tcbInfo.TdxModule.Attributes.Bytes, attributesMask) { return fmt.Errorf("AttributesMask value(%q) is not equal to TdxModule.Attributes field in Intel PCS's reported TDX TCB info(%q)", hex.EncodeToString(attributesMask), hex.EncodeToString(tdQuoteBodyOptions.tcbInfo.TdxModule.Attributes.Bytes)) } if err := checkTcbInfoTcbStatus(tdQuoteBodyOptions.tcbInfo, tdQuoteBody, tdQuoteBodyOptions.pckCertExtensions); err != nil { return fmt.Errorf("TDX TCB info reported by Intel PCS failed TCB status check: %v", err) } return nil } func verifyQeReport(qeReport *pb.EnclaveReport, qeReportOptions *qeReportOptions) error { if len(qeReportOptions.qeIdentity.MiscselectMask.Bytes) != 4 { // To create a uint32 variable, byte array should have size 4 return fmt.Errorf("MISCSELECTMask field size(%d) in Intel PCS's reported QE Identity is not equal to expected size(4)", len(qeReportOptions.qeIdentity.MiscselectMask.Bytes)) } if len(qeReportOptions.qeIdentity.Miscselect.Bytes) != 4 { // To create a uint32 variable, byte array should have size 4 return fmt.Errorf("MISCSELECT field size(%d) in Intel PCS's reported QE Identity is not equal to expected size(4)", len(qeReportOptions.qeIdentity.Miscselect.Bytes)) } miscSelectMask := binary.LittleEndian.Uint32(qeReportOptions.qeIdentity.MiscselectMask.Bytes) miscSelect := binary.LittleEndian.Uint32(qeReportOptions.qeIdentity.Miscselect.Bytes) miscSelectMask = qeReport.GetMiscSelect() & miscSelectMask logger.V(2).Infof("MISCSELECT value from Intel PCS's reported QE Identity is %v, and MISCSELECTMask value is %v", miscSelect, miscSelectMask) if miscSelectMask != miscSelect { return fmt.Errorf("MISCSELECT value(%v) from Intel PCS's reported QE Identity is not equal to MISCSELECTMask value(%v)", miscSelect, miscSelectMask) } if len(qeReportOptions.qeIdentity.AttributesMask.Bytes) != len(qeReport.GetAttributes()) { return fmt.Errorf("size of AttributesMask value(%d) in Intel PCS's reported QE Identity is not equal to size of SeamAttributes value(%d) in QE Report", len(qeReportOptions.qeIdentity.AttributesMask.Bytes), len(qeReport.GetAttributes())) } qeAttributesMask := applyMask(qeReportOptions.qeIdentity.AttributesMask.Bytes, qeReport.GetAttributes()) logger.V(2).Infof("AttributesMask value is %v, and Attributes value in Intel PCS's reported QE Identity is %v", qeAttributesMask, qeReportOptions.qeIdentity.Attributes) if !bytes.Equal(qeReportOptions.qeIdentity.Attributes.Bytes, qeAttributesMask) { return fmt.Errorf("AttributesMask value(%v) is not equal to Attributes value(%v) in Intel PCS's reported QE Identity", qeAttributesMask, qeReportOptions.qeIdentity.Attributes) } logger.V(2).Infof("MRSIGNER value in QE Report is %q, and MRSIGNER value in Intel PCS's reported QE Identity is %q", hex.EncodeToString(qeReport.GetMrSigner()), qeReportOptions.qeIdentity.Mrsigner) if !bytes.Equal(qeReportOptions.qeIdentity.Mrsigner.Bytes, qeReport.GetMrSigner()) { return fmt.Errorf("MRSIGNER value(%q) in QE Report is not equal to MRSIGNER value(%q) in Intel PCS's reported QE Identity", hex.EncodeToString(qeReport.GetMrSigner()), qeReportOptions.qeIdentity.Mrsigner) } logger.V(2).Infof("ISV PRODID value in QE Report is %v, and ISV PRODID value in Intel PCS's reported QE Identity is %v", qeReport.GetIsvProdId(), qeReportOptions.qeIdentity.IsvProdID) if qeReport.GetIsvProdId() != uint32(qeReportOptions.qeIdentity.IsvProdID) { return fmt.Errorf("ISV PRODID value(%v) in QE Report is not equal to ISV PRODID value(%v) in Intel PCS's reported QE Identity", qeReport.GetIsvProdId(), qeReportOptions.qeIdentity.IsvProdID) } if err := checkQeTcbStatus(qeReportOptions.qeIdentity.TcbLevels, qeReport.GetIsvSvn()); err != nil { return fmt.Errorf("QE Identity reported by Intel PCS failed TCB status check: %v", err) } return nil } func verifyQuote(quote *pb.QuoteV4, options *Options) error { chain := options.chain collateral := options.collateral pckCertExtensions := options.pckCertExtensions attestkey := quote.GetSignedData().GetEcdsaAttestationKey() logger.V(1).Info("Extracting attestation key from the quote") attestPublicKey, err := bytesToEcdsaPubKey(attestkey) if err != nil { return fmt.Errorf("attestation key in the quote is invalid: %v", err) } logger.V(1).Info("Attestation key extracted successfully from the quote") logger.V(1).Info("Extracting signature present in the quote") signature := quote.GetSignedData().GetSignature() signature, err = abi.SignatureToDER(signature) if err != nil { return fmt.Errorf("unable to convert QuoteV4's signature to DER format: %v", err) } logger.V(1).Info("Signature extracted successfully from the quote") logger.V(1).Info("Verifying Header and TD Quote Body using attestation key and signature present in the quote") message, err := getHeaderAndTdQuoteBodyInAbiBytes(quote) if err != nil { return fmt.Errorf("could not get message digest for verification: %v", err) } hashedMessage := sha256.Sum256(message) // A hashed version of the attestation key is used as a nonce while generating the signed QE report which in turn is verified with the PCK certificate chain. // Thus it is safe to use its raw public key here even though it doesn't have an associated certificate. if !ecdsa.VerifyASN1(attestPublicKey, hashedMessage[:], signature) { return ErrHashVerificationFail } logger.V(1).Info("Header and TD Quote Body verified successfully") qeReportCertificationData := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData() logger.V(1).Info("Verifying the QE Report signature using PCK Leaf certificate") if err := tdxProtoQeReportSignature(qeReportCertificationData, chain.PCKCertificate); err != nil { return fmt.Errorf("error verifying QE report signature: %v", err) } logger.V(1).Info("QE Report signature verified successfully") logger.V(1).Info("Verifying QE Report Data") if err := verifyHash256(quote); err != nil { return fmt.Errorf("error verifying QE report data: %v", err) } logger.V(1).Info("QE Report Data verified successfully") if collateral != nil { logger.V(1).Info("Verifying TD Quote Body using TCB Info API response") if err := verifyTdQuoteBody(quote.GetTdQuoteBody(), &tdQuoteBodyOptions{ tcbInfo: collateral.TdxTcbInfo.TcbInfo, pckCertExtensions: pckCertExtensions, }); err != nil { return err } logger.V(1).Info("TD Quote Body verified successfully") logger.V(1).Info("Verifying QE Report using QE Identity API response") if err := verifyQeReport(qeReportCertificationData.GetQeReport(), &qeReportOptions{ qeIdentity: &collateral.QeIdentity.EnclaveIdentity, }); err != nil { return err } logger.V(1).Info("QE Report verified successfully") } return nil } func tdxProtoQeReportSignature(qeReportCertificationData *pb.QEReportCertificationData, pckCert *x509.Certificate) error { rawReport, err := abi.EnclaveReportToAbiBytes(qeReportCertificationData.GetQeReport()) if err != nil { return fmt.Errorf("could not parse QE report: %v", err) } return tdxQeReportSignature(rawReport, qeReportCertificationData.GetQeReportSignature(), pckCert) } func tdxQeReportSignature(qeReport []byte, signature []byte, pckCert *x509.Certificate) error { derSignature, err := abi.SignatureToDER(signature) if err != nil { return fmt.Errorf("unable to convert QE report's signature to DER format: %v", err) } if err := pckCert.CheckSignature(x509.ECDSAWithSHA256, qeReport, derSignature); err != nil { return fmt.Errorf("QE report's signature verification using PCK Leaf Certificate failed: %v", err) } logger.V(1).Info("QE Report's signature verified using PCK Leaf Certificate") return nil } func verifyResponse(signingPhrase string, rootCertificate *x509.Certificate, signingCertificate *x509.Certificate, rawBody []byte, rawSignature string, crl *x509.RevocationList, options *Options) error { logger.V(1).Info("Verifying root certificate in the issuer chain") if err := validateCertificate(rootCertificate, rootCertificate, rootCertPhrase); err != nil { return fmt.Errorf("unable to validate root certificate in the issuer chain: %v", err) } logger.V(1).Info("Root certificate verified successfully in the issuer chain") logger.V(1).Info("Verifiying signing certificate in the issuer chain") if err := validateCertificate(signingCertificate, rootCertificate, signingPhrase); err != nil { return fmt.Errorf("unable to validate signing certificate in the issuer chain: %v", err) } logger.V(1).Info("Signing certificate verified successfully in the issuer chain") if _, err := signingCertificate.Verify(x509Options(options.TrustedRoots, nil, options.Now)); err != nil { return fmt.Errorf("unable to verify signing certificate: %v", err) } logger.V(1).Info("Signing certificate successfully verified using trusted roots") signature, err := hex.DecodeString(rawSignature) if err != nil { return fmt.Errorf("unable to decode signature string in the response: %v", err) } derSignature, err := abi.SignatureToDER(signature) if err != nil { return fmt.Errorf("unable to convert signature to DER format: %v", err) } logger.V(1).Info("Verifying response body using signing certificate") if err := signingCertificate.CheckSignature(x509.ECDSAWithSHA256, rawBody, derSignature); err != nil { return fmt.Errorf("could not verify response body using the signing certificate: %v", err) } logger.V(1).Info("Response body verified successfully using signing certificate") if options.CheckRevocations { if options.GetCollateral { logger.V(1).Info("Verifying Root CA CRL using root certificate in the issuer's chain") if err := validateCRL(crl, rootCertificate); err != nil { return fmt.Errorf("root CA CRL verification failed using root certificate in the issuer's chain: %v", err) } logger.V(1).Info("Root CA CRL verified successfully using root certificate in the issuer's chain") for _, bad := range crl.RevokedCertificates { if signingCertificate.SerialNumber.Cmp(bad.SerialNumber) == 0 { return fmt.Errorf("signing certificate was revoked at %v", bad.RevocationTime) } } logger.V(1).Info("Root certificate is not revoked by the signing certificate") } else { return ErrRevocationCheckFailed } } return nil } func verifyTCBinfo(options *Options) error { collateral := options.collateral tcbInfo := collateral.TdxTcbInfo.TcbInfo signature := collateral.TdxTcbInfo.Signature logger.V(2).Infof("TcbInfo ID is %q, and expected ID is %q", tcbInfo.ID, tcbInfoID) if tcbInfo.ID != tcbInfoID { return fmt.Errorf("tcbInfo ID %q does not match with expected ID %q", tcbInfo.ID, tcbInfoID) } logger.V(2).Infof("TcbInfo version is %v, and expected version is %v", tcbInfo.Version, tcbInfoVersion) if tcbInfo.Version != tcbInfoVersion { return fmt.Errorf("tcbInfo version %v does not match with expected version %v", tcbInfo.Version, tcbInfoVersion) } if len(tcbInfo.TcbLevels) == 0 { return ErrTcbInfoTcbLevelsMissing } logger.V(1).Info("Verifying TCB Info response") if err := verifyResponse(tcbSigningPhrase, collateral.TcbInfoIssuerRootCertificate, collateral.TcbInfoIssuerIntermediateCertificate, collateral.TcbInfoBody, signature, collateral.RootCaCrl, options); err != nil { return fmt.Errorf("tcbInfo response verification failed: %v", err) } return nil } func verifyQeIdentity(options *Options) error { collateral := options.collateral qeIdentity := collateral.QeIdentity.EnclaveIdentity signature := collateral.QeIdentity.Signature logger.V(2).Infof("QeIdentity ID is %q, and expected ID is %q", qeIdentity.ID, qeIdentityID) if qeIdentity.ID != qeIdentityID { return fmt.Errorf("QeIdentity ID %q does not match with expected ID %q", qeIdentity.ID, qeIdentityID) } logger.V(2).Infof("QeIdentity version is %v, and expected version is %v", qeIdentity.Version, qeIdentityVersion) if qeIdentity.Version != qeIdentityVersion { return fmt.Errorf("QeIdentity version %v does not match with expected version %v", qeIdentity.Version, qeIdentityVersion) } if len(qeIdentity.TcbLevels) == 0 { return ErrQeIdentityTcbLevelsMissing } logger.V(1).Info("Verifying QE Identity response") if err := verifyResponse(tcbSigningPhrase, collateral.QeIdentityIssuerRootCertificate, collateral.QeIdentityIssuerIntermediateCertificate, collateral.EnclaveIdentityBody, signature, collateral.RootCaCrl, options); err != nil { return fmt.Errorf("QeIdentity response verification failed: %v", err) } return nil } func verifyEvidence(quote any, options *Options) error { switch q := quote.(type) { case *pb.QuoteV4: return verifyEvidenceV4(q, options) default: return fmt.Errorf("unsupported quote type: %T", quote) } } func verifyEvidenceV4(quote *pb.QuoteV4, options *Options) error { if quote.GetHeader().GetTeeType() != abi.TeeTDX { return abi.ErrTeeType } logger.V(1).Info("Verifying the PCK Certificate Chain in the quote") if err := verifyPCKCertificationChain(options); err != nil { return err } logger.V(1).Info("PCK Certificate Chain successfully verified") if options.GetCollateral { logger.V(1).Info("Verifying the collaterals obtained from the Intel PCS") if err := verifyCollateral(options); err != nil { return fmt.Errorf("could not verify collaterals obtained: %v", err) } logger.V(1).Info("Verifying the TCB Info API response") if err := verifyTCBinfo(options); err != nil { return err } logger.V(1).Info("TCB Info API response verified successfully") logger.V(1).Info("Verifying the QE Identity API response") if err := verifyQeIdentity(options); err != nil { return err } logger.V(1).Info("QE Identity API response verified successfully") logger.V(1).Info("Collaterals verified successfully") } return verifyQuote(quote, options) } // TdxQuote verifies the protobuf representation of an attestation quote's signature // based on the quote's SignatureAlgo, provided the certificate chain is valid for // formats - QuoteV4. func TdxQuote(quote any, options *Options) error { if options == nil { return ErrOptionsNil } switch q := quote.(type) { case *pb.QuoteV4: return tdxQuoteV4(q, options) default: return fmt.Errorf("unsupported quote type: %T", quote) } } // tdxQuoteV4 verifies the QuoteV4 protobuf representation of an attestation quote's signature // based on the quote's SignatureAlgo, provided the certificate chain is valid. func tdxQuoteV4(quote *pb.QuoteV4, options *Options) error { logger.V(1).Info("Checking that the quote parameters meet the required size") logger.V(2).Info("Quote Version found: ", quote.Header.Version) logger.V(2).Infof("Quote TeeType found: 0x%x", quote.Header.TeeType) if err := abi.CheckQuoteV4(quote); err != nil { return fmt.Errorf("QuoteV4 invalid: %v", err) } logger.V(1).Info("Quote parameters meet the required size") logger.V(1).Info("Extracting PCK certificate chain from the quote") chain, err := extractChainFromQuoteV4(quote) if err != nil { return err } exts, err := pcs.PckCertificateExtensions(chain.PCKCertificate) if err != nil { return fmt.Errorf("could not get PCK certificate extensions: %v", err) } logger.V(2).Info("PCK Leaf Certificate Issuer organization: ", chain.PCKCertificate.Issuer.Organization) logger.V(2).Info("PCK Leaf Certificate FMSPC value: ", exts.FMSPC) logger.V(1).Info("PCK Certificate Chain extracted successfully") var collateral *Collateral if options.GetCollateral { logger.V(1).Info("Obtaining collaterals using APIs from the Intel PCS") ca, err := extractCaFromPckCert(chain.PCKCertificate) if err != nil { return err } collateral, err = obtainCollateral(exts.FMSPC, ca, options) if err != nil { return err } logger.V(1).Info("Collaterals successfully obtained using the APIs from the Intel PCS") } options.collateral = collateral options.pckCertExtensions = exts options.chain = chain if options.Now.IsZero() { options.Now = time.Now() } return verifyEvidenceV4(quote, options) } // RawTdxQuote verifies the raw bytes representation of an attestation quote func RawTdxQuote(raw []byte, options *Options) error { quote, err := abi.QuoteToProto(raw) if err != nil { return fmt.Errorf("could not convert raw bytes to QuoteV4: %v", err) } return TdxQuote(quote, options) } func getTrustedRoots(rot *ccpb.RootOfTrust) (*x509.CertPool, error) { if len(rot.CabundlePaths) == 0 && len(rot.Cabundles) == 0 { return nil, nil } result := x509.NewCertPool() for _, path := range rot.CabundlePaths { certBytes, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("could not parse CA bundle %q: %v", path, err) } if !result.AppendCertsFromPEM(certBytes) { return nil, fmt.Errorf("CA bundle %q did not contain certs", path) } } for i, cabundle := range rot.Cabundles { if !result.AppendCertsFromPEM([]byte(cabundle)) { return nil, fmt.Errorf("CA bundle %d did not contain certs", i) } } return result, nil } // RootOfTrustToOptions translates the RootOfTrust message into the Options type needed // for driving an attestation verification. func RootOfTrustToOptions(rot *ccpb.RootOfTrust) (*Options, error) { trustedRoots, err := getTrustedRoots(rot) if err != nil { return nil, err } return &Options{ CheckRevocations: rot.CheckCrl, GetCollateral: rot.GetCollateral, TrustedRoots: trustedRoots, }, nil } // Parse root certificates from the embedded trusted_root certificate file. func init() { root, _ := pem.Decode(defaultRootCertByte) trustedRootCertificate, _ = x509.ParseCertificate(root.Bytes) // Initialize logger logger.Init("", false, false, os.Stdout) } go-tdx-guest-0.3.1/verify/verify_test.go000066400000000000000000000636331456371376300202720ustar00rootroot00000000000000// 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 verify import ( "crypto/x509" "encoding/hex" "os" "reflect" "testing" "time" "github.com/google/go-tdx-guest/abi" "github.com/google/go-tdx-guest/pcs" pb "github.com/google/go-tdx-guest/proto/tdx" testcases "github.com/google/go-tdx-guest/testing" "github.com/google/go-tdx-guest/testing/testdata" "github.com/google/logger" ) var ( // Adjust currentTime to compare against so that the validity with respect to time is always true. currentTime = time.Date(2023, time.July, 1, 1, 0, 0, 0, time.UTC) // Adjust futureTime to compare against so that the validity with respect to time fails. futureTime = time.Date(2053, time.July, 1, 1, 0, 0, 0, time.UTC) ) func setTcbSvnValues(sgxSvn byte, tdxSvn byte, tdxTcbcomponents *[]pcs.TcbComponent, sgxTcbcomponents *[]pcs.TcbComponent) { sgxComponents := *sgxTcbcomponents tdxComponents := *tdxTcbcomponents for i := 0; i < len(sgxComponents); i++ { sgxComponents[i].Svn = sgxSvn tdxComponents[i].Svn = tdxSvn } } func TestMain(m *testing.M) { logger.Init("VerifyTestLog", false, false, os.Stderr) os.Exit(m.Run()) } func TestParsePckChain(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } if _, err := extractChainFromQuote(quote); err != nil { t.Fatal(err) } certChain := quote.(*pb.QuoteV4).SignedData.CertificationData.QeReportCertificationData.PckCertificateChainData.PckCertChain certChain = append(certChain, 0) quote.(*pb.QuoteV4).SignedData.CertificationData.QeReportCertificationData.PckCertificateChainData.PckCertChain = certChain if _, err := extractChainFromQuote(quote); err != nil { t.Error(err) } certChain[len(certChain)-1] = 1 quote.(*pb.QuoteV4).SignedData.CertificationData.QeReportCertificationData.PckCertificateChainData.PckCertChain = certChain if _, err := extractChainFromQuote(quote); err == nil { t.Error("Expected error but got none") } } func TestPckCertificateExtensions(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } chain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } pckExt := &pcs.PckExtensions{} ppidBytes := []byte{8, 157, 223, 219, 156, 3, 89, 200, 42, 59, 199, 113, 146, 57, 87, 78} fmspcBytes := []byte{80, 128, 111, 0, 0, 0} pceIDBytes := []byte{0, 0} pckExt.PPID = hex.EncodeToString(ppidBytes) pckExt.FMSPC = hex.EncodeToString(fmspcBytes) pckExt.PCEID = hex.EncodeToString(pceIDBytes) pckExtTcb := &pcs.PckCertTCB{ PCESvn: 11, CPUSvn: []byte{3, 3, 2, 2, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}, CPUSvnComponents: []byte{3, 3, 2, 2, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}, } pckExt.TCB = *pckExtTcb ext, err := pcs.PckCertificateExtensions(chain.PCKCertificate) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(ext, pckExt) { t.Errorf("PCK certificate's extension(%v), does not match with expected extension(%v)", ext, pckExt) } } func TestVerifyPckChainWithoutRevocation(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } pckChain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } if err := verifyEvidence(quote, &Options{CheckRevocations: false, GetCollateral: false, chain: pckChain, Now: currentTime}); err != nil { t.Error(err) } } func TestNegativeVerifyPckChainWithoutRevocation(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } pckChain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } wantErr := "error verifying PCK Certificate: x509: certificate has expired or is not yet valid: current time 2053-07-01T01:00:00Z is after 2029-09-20T13:20:31Z (true)" if err := verifyEvidence(quote, &Options{CheckRevocations: false, GetCollateral: false, chain: pckChain, Now: futureTime}); err == nil || err.Error() != wantErr { t.Errorf("Certificates Expired: verifyEvidence() = %v. Want error: %v.", err, wantErr) } } func TestVerifyPckLeafCertificate(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } pckChain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } pckLeafCert := pckChain.PCKCertificate opts := &Options{CheckRevocations: false, GetCollateral: false, TrustedRoots: nil, chain: pckChain} chains, err := pckLeafCert.Verify(x509Options(opts.TrustedRoots, pckChain.IntermediateCertificate, opts.Now)) if err != nil { t.Fatal(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(pckChain.PCKCertificate) { t.Errorf("PCK verification chain did not start with the PCK Leaf certificate: %v", chains[0][0]) } if !chains[0][1].Equal(pckChain.IntermediateCertificate) { t.Errorf("PCK verification chain did not step to with the Intermediate CA certificate: %v", chains[0][1]) } if !chains[0][2].Equal(trustedRootCertificate) { t.Errorf("PCK verification chain did not end with the Trusted Root certificate: %v", chains[0][2]) } } func TestValidateX509Certificate(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } pckChain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } if err := validateX509Cert(pckChain.PCKCertificate, 3, x509.ECDSAWithSHA256, x509.ECDSA, "P-256"); err != nil { t.Error(err) } } func TestNegativeValidateX509Certificate(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } pckChain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } type input struct { cert *x509.Certificate version int signatureAlgorithm x509.SignatureAlgorithm publicKeyAlgorithm x509.PublicKeyAlgorithm curve string } tests := []struct { name string input input wantErr string }{ { name: "Version Invalid", input: input{ cert: pckChain.PCKCertificate, version: 4, signatureAlgorithm: x509.ECDSAWithSHA256, publicKeyAlgorithm: x509.ECDSA, curve: "P-256", }, wantErr: "certificate's version found 3. Expected 4", }, { name: "Signature Algorithm Invalid", input: input{ cert: pckChain.PCKCertificate, version: 3, signatureAlgorithm: x509.ECDSAWithSHA1, publicKeyAlgorithm: x509.ECDSA, curve: "P-256", }, wantErr: "certificate's signature algorithm found ECDSA-SHA256. Expected ECDSA-SHA1", }, { name: "Public Key Algorithm Invalid", input: input{ cert: pckChain.PCKCertificate, version: 3, signatureAlgorithm: x509.ECDSAWithSHA256, publicKeyAlgorithm: x509.Ed25519, curve: "P-256", }, wantErr: "certificate's public Key algorithm found ECDSA. Expected Ed25519", }, { name: "Public Key Curve Invalid", input: input{ cert: pckChain.PCKCertificate, version: 3, signatureAlgorithm: x509.ECDSAWithSHA256, publicKeyAlgorithm: x509.ECDSA, curve: "P-300", }, wantErr: `certificate's public key curve is "P-256". Expected "P-300"`, }, } for _, tc := range tests { if err := validateX509Cert(tc.input.cert, tc.input.version, tc.input.signatureAlgorithm, tc.input.publicKeyAlgorithm, tc.input.curve); err == nil || err.Error() != tc.wantErr { t.Errorf("%s: validateX509Cert() = %v. Want error %v", tc.name, err, tc.wantErr) } } } func TestRawQuoteVerifyWithoutCollateral(t *testing.T) { options := &Options{CheckRevocations: false, GetCollateral: false, Now: currentTime} if err := RawTdxQuote(testdata.RawQuote, options); err != nil { t.Error(err) } } func TestVerifyQuoteV4(t *testing.T) { anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("Quote is not a QuoteV4") } pckChain, err := extractChainFromQuote(anyQuote) if err != nil { t.Fatal(err) } options := &Options{CheckRevocations: false, GetCollateral: false, chain: pckChain, Now: currentTime} if err := verifyQuote(quote, options); err != nil { t.Error(err) } } func TestNegativeVerification(t *testing.T) { tests := []struct { name string changeIndex int changeValue byte wantErr string }{ { name: "Version byte Changed", changeIndex: 0x00, changeValue: 3, wantErr: "could not convert raw bytes to QuoteV4: quote format not supported", }, { name: "Signed data size byte Changed", changeIndex: 0x278, changeValue: 0x10, wantErr: "could not convert raw bytes to QuoteV4: size of certificate data is 0xf8a. Expected size 0x1045", }, { name: "Certificate chain byte Changed", changeIndex: 0x1343, changeValue: 0x32, wantErr: ErrPCKCertChainInvalid.Error(), }, { name: "Root Certificate byte Changed", changeIndex: 0x1329, changeValue: 0x32, wantErr: "unable to validate root cert: certificate signature verification using parent certificate failed: x509: ECDSA verification failure", }, { name: "Intermediate Certificate byte Changed", changeIndex: 0xF5F, changeValue: 0x32, wantErr: `unable to validate Intermediate CA certificate: certificate signature verification using parent certificate failed: x509: ECDSA verification failure`, }, { name: "PCK Certificate byte Changed", changeIndex: 0xB77, changeValue: 0x32, wantErr: `unable to validate PCK leaf certificate: certificate signature verification using parent certificate failed: x509: ECDSA verification failure`, }, { name: "Header Byte Changed", changeIndex: 0x1E, changeValue: 0x32, wantErr: "unable to verify message digest using quote's signature and ecdsa attestation key", }, { name: "TD Quote Body Changed", changeIndex: 0x3C, changeValue: 0x32, wantErr: "unable to verify message digest using quote's signature and ecdsa attestation key", }, } options := &Options{CheckRevocations: false, GetCollateral: false, TrustedRoots: nil, Now: currentTime} rawQuote := make([]byte, len(testdata.RawQuote)) for _, tc := range tests { copy(rawQuote, testdata.RawQuote) rawQuote[tc.changeIndex] = tc.changeValue if err := RawTdxQuote(rawQuote, options); err == nil || err.Error() != tc.wantErr { t.Errorf("%s: RawTdxQuote() = %v. Want error %v", tc.name, err, tc.wantErr) } } } func TestGetPckCrl(t *testing.T) { getter := testcases.TestGetter ca := platformIssuerID collateral := &Collateral{} if err := getPckCrl(ca, getter, collateral); err != nil { t.Error(err) } } func TestGetTcbInfo(t *testing.T) { getter := testcases.TestGetter fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) collateral := &Collateral{} if err := getTcbInfo(fmspc, getter, collateral); err != nil { t.Error(err) } } func TestGetQeIdentity(t *testing.T) { getter := testcases.TestGetter collateral := &Collateral{} if err := getQeIdentity(getter, collateral); err != nil { t.Error(err) } } func TestGetRootCRL(t *testing.T) { getter := testcases.TestGetter collateral := &Collateral{} if err := getQeIdentity(getter, collateral); err != nil { t.Fatal(err) } if err := getRootCrl(getter, collateral); err != nil { t.Error(err) } } func TestExtractFmspcAndCaFromPckCert(t *testing.T) { quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } chain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } ca, err := extractCaFromPckCert(chain.PCKCertificate) if err != nil { t.Fatal(err) } if ca != platformIssuerID { t.Errorf("ca extracted from PCK certificate (%q), does not match with expected ca (%q)", ca, platformIssuerID) } fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) exts, err := pcs.PckCertificateExtensions(chain.PCKCertificate) if err != nil { t.Fatal(err) } if exts.FMSPC != fmspc { t.Errorf("fmspc extracted from PCK cert(%v), does not match with expected fmspc(%v)", exts.FMSPC, fmspc) } } func TestObtainAndVerifyCollateral(t *testing.T) { getter := testcases.TestGetter ca := platformIssuerID fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) options := &Options{GetCollateral: true, CheckRevocations: true, Getter: getter, Now: currentTime} collateral, err := obtainCollateral(fmspc, ca, options) if err != nil { t.Fatal(err) } options.collateral = collateral if err := verifyCollateral(options); err != nil { t.Error(err) } } func TestNegativeObtainAndVerifyCollateral(t *testing.T) { getter := testcases.TestGetter ca := platformIssuerID fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) options := &Options{GetCollateral: true, CheckRevocations: true, Getter: getter, Now: futureTime} collateral, err := obtainCollateral(fmspc, ca, options) if err != nil { t.Fatal(err) } options.collateral = collateral wantErr := "tcbInfo has expired" if err := verifyCollateral(options); err == nil || err.Error() != wantErr { t.Errorf("Collaterals Expired: verifyCollateral() = %v. Want error %v", err, wantErr) } } func TestVerifyUsingTcbInfoV4(t *testing.T) { getter := testcases.TestGetter fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) collateral := &Collateral{} if err := getTcbInfo(fmspc, getter, collateral); err != nil { t.Fatal(err) } anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } chain, err := extractChainFromQuote(anyQuote) if err != nil { t.Fatal(err) } ext, err := pcs.PckCertificateExtensions(chain.PCKCertificate) if err != nil { t.Fatal(err) } tcbInfo := collateral.TdxTcbInfo.TcbInfo // Due to updated SVN values in the sample response, it will result in TCB status failure, // when compared to the TD Quote Body's TeeTcbSvn value. // For the purpose of testing, converting all SVNs value to 0 setTcbSvnValues(0, 0, &tcbInfo.TcbLevels[0].Tcb.TdxTcbcomponents, &tcbInfo.TcbLevels[0].Tcb.SgxTcbcomponents) quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("quote is not a QuoteV4") } if err := verifyTdQuoteBody(quote.GetTdQuoteBody(), &tdQuoteBodyOptions{tcbInfo: tcbInfo, pckCertExtensions: ext}); err != nil { t.Error(err) } } func TestNegativeVerifyUsingTcbInfoV4(t *testing.T) { getter := testcases.TestGetter fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) collateral := &Collateral{} if err := getTcbInfo(fmspc, getter, collateral); err != nil { t.Fatal(err) } anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("quote is not a QuoteV4") } chain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } ext, err := pcs.PckCertificateExtensions(chain.PCKCertificate) if err != nil { t.Fatal(err) } tcbInfo := collateral.TdxTcbInfo.TcbInfo var sampleTcbInfo pcs.TdxTcbInfo sampleTcbInfo.TcbInfo = tcbInfo sampleTcbInfo.TcbInfo.Fmspc = "11111f000000" wantErr := `FMSPC from PCK Certificate("50806f000000") is not equal to FMSPC value from Intel PCS's reported TDX TCB info("11111f000000")` if err := verifyTdQuoteBody(quote.GetTdQuoteBody(), &tdQuoteBodyOptions{tcbInfo: sampleTcbInfo.TcbInfo, pckCertExtensions: ext}); err == nil || err.Error() != wantErr { t.Errorf("FMSPC value changed: VerifyTdQuoteBody() = %v. Want error %v", err, wantErr) } sampleTcbInfo.TcbInfo = tcbInfo sampleTcbInfo.TcbInfo.PceID = "1111" wantErr = `PCEID from PCK Certificate("0000") is not equal to PCEID from Intel PCS's reported TDX TCB info("1111")` if err := verifyTdQuoteBody(quote.GetTdQuoteBody(), &tdQuoteBodyOptions{tcbInfo: sampleTcbInfo.TcbInfo, pckCertExtensions: ext}); err == nil || err.Error() != wantErr { t.Errorf("PCEID value changed: verifyUsingTcbInfo() = %v. Want error %v", err, wantErr) } sampleTcbInfo.TcbInfo = tcbInfo sampleTcbInfo.TcbInfo.TdxModule.Mrsigner.Bytes = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} wantErr = `MRSIGNERSEAM value from TD Quote Body("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") is not equal to TdxModule.Mrsigner field in Intel PCS's reported TDX TCB info("0102030405060708090a")` if err := verifyTdQuoteBody(quote.GetTdQuoteBody(), &tdQuoteBodyOptions{tcbInfo: sampleTcbInfo.TcbInfo, pckCertExtensions: ext}); err == nil || err.Error() != wantErr { t.Errorf("Mrsigner value changed: verifyUsingTcbInfo() = %v. Want error %v", err, wantErr) } sampleTcbInfo.TcbInfo = tcbInfo sampleTcbInfo.TcbInfo.TdxModule.Attributes.Bytes = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} wantErr = `AttributesMask value("0000000000000000") is not equal to TdxModule.Attributes field in Intel PCS's reported TDX TCB info("0102030405060708090a")` if err := verifyTdQuoteBody(quote.GetTdQuoteBody(), &tdQuoteBodyOptions{tcbInfo: sampleTcbInfo.TcbInfo, pckCertExtensions: ext}); err == nil || err.Error() != wantErr { t.Errorf("Attributes value changed: verifyUsingTcbInfo() = %v. Want error %v", err, wantErr) } } func TestVerifyUsingQeIdentityV4(t *testing.T) { getter := testcases.TestGetter collateral := &Collateral{} if err := getQeIdentity(getter, collateral); err != nil { t.Fatal(err) } anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("Quote is not a QuoteV4") } qeIdentity := collateral.QeIdentity.EnclaveIdentity qeReport := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData().GetQeReport() if err := verifyQeReport(qeReport, &qeReportOptions{qeIdentity: &qeIdentity}); err != nil { t.Error(err) } } func TestNegativeVerifyUsingQeIdentityV4(t *testing.T) { getter := testcases.TestGetter collateral := &Collateral{} if err := getQeIdentity(getter, collateral); err != nil { t.Fatal(err) } anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("quote is not a QuoteV4") } qeIdentity := collateral.QeIdentity.EnclaveIdentity qeReport := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData().GetQeReport() var sampleQeIdentity pcs.QeIdentity sampleQeIdentity.EnclaveIdentity = qeIdentity sampleQeIdentity.EnclaveIdentity.Miscselect.Bytes = []byte{1, 2, 3, 4} wantErr := "MISCSELECT value(67305985) from Intel PCS's reported QE Identity is not equal to MISCSELECTMask value(0)" if err := verifyQeReport(qeReport, &qeReportOptions{qeIdentity: &sampleQeIdentity.EnclaveIdentity}); err == nil || err.Error() != wantErr { t.Errorf("Miscselect value changed: verifyUsingQeIdentity() = %v. Want error %v", err, wantErr) } sampleQeIdentity.EnclaveIdentity = qeIdentity sampleQeIdentity.EnclaveIdentity.Attributes.Bytes = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} wantErr = "AttributesMask value([17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]) is not equal to Attributes value({[1 2 3 4 5 6 7 8 9 10]}) in Intel PCS's reported QE Identity" if err := verifyQeReport(qeReport, &qeReportOptions{qeIdentity: &sampleQeIdentity.EnclaveIdentity}); err == nil || err.Error() != wantErr { t.Errorf("Attributes value changed: verifyUsingQeIdentity() = %v. Want error %v", err, wantErr) } sampleQeIdentity.EnclaveIdentity = qeIdentity sampleQeIdentity.EnclaveIdentity.Mrsigner.Bytes = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} wantErr = `MRSIGNER value("dc9e2a7c6f948f17474e34a7fc43ed030f7c1563f1babddf6340c82e0e54a8c5") in QE Report is not equal to MRSIGNER value({"\x01\x02\x03\x04\x05\x06\a\b\t\n"}) in Intel PCS's reported QE Identity` if err := verifyQeReport(qeReport, &qeReportOptions{qeIdentity: &sampleQeIdentity.EnclaveIdentity}); err == nil || err.Error() != wantErr { t.Errorf("Mrsigner value changed: verifyUsingQeIdentity() = %v. Want error %v", err, wantErr) } sampleQeIdentity.EnclaveIdentity = qeIdentity sampleQeIdentity.EnclaveIdentity.IsvProdID = 5 wantErr = "ISV PRODID value(2) in QE Report is not equal to ISV PRODID value(5) in Intel PCS's reported QE Identity" if err := verifyQeReport(qeReport, &qeReportOptions{qeIdentity: &sampleQeIdentity.EnclaveIdentity}); err == nil || err.Error() != wantErr { t.Errorf("IsvProdID value changed: verifyUsingQeIdentity() = %v. Want error %v", err, wantErr) } } func TestNegativeTcbInfoTcbStatusV4(t *testing.T) { getter := testcases.TestGetter fmspcBytes := []byte{80, 128, 111, 0, 0, 0} fmspc := hex.EncodeToString(fmspcBytes) collateral := &Collateral{} if err := getTcbInfo(fmspc, getter, collateral); err != nil { t.Fatal(err) } anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("quote is not a QuoteV4") } chain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } ext, err := pcs.PckCertificateExtensions(chain.PCKCertificate) if err != nil { t.Fatal(err) } tcbInfo := collateral.TdxTcbInfo.TcbInfo setTcbSvnValues(10, 0, &tcbInfo.TcbLevels[0].Tcb.TdxTcbcomponents, &tcbInfo.TcbLevels[0].Tcb.SgxTcbcomponents) wantErr := "no matching TCB level found" if err := checkTcbInfoTcbStatus(tcbInfo, quote.GetTdQuoteBody(), ext); err == nil || err.Error() != wantErr { t.Errorf("SgxTcbComponents values greater: checkTcbInfoTcbStatus() = %v. Want error %v", err, wantErr) } setTcbSvnValues(0, 10, &tcbInfo.TcbLevels[0].Tcb.TdxTcbcomponents, &tcbInfo.TcbLevels[0].Tcb.SgxTcbcomponents) if err := checkTcbInfoTcbStatus(tcbInfo, quote.GetTdQuoteBody(), ext); err == nil || err.Error() != wantErr { t.Errorf("TdxTcbComponents values greater: checkTcbInfoTcbStatus() = %v. Want error %v", err, wantErr) } tcbInfo.TcbLevels[0].Tcb.Pcesvn = 20 setTcbSvnValues(0, 0, &tcbInfo.TcbLevels[0].Tcb.TdxTcbcomponents, &tcbInfo.TcbLevels[0].Tcb.SgxTcbcomponents) if err := checkTcbInfoTcbStatus(tcbInfo, quote.GetTdQuoteBody(), ext); err == nil || err.Error() != wantErr { t.Errorf("PCESvn value greater: checkTcbInfoTcbStatus() = %v. Want error %v", err, wantErr) } tcbInfo.TcbLevels[0].Tcb.Pcesvn = 0 tcbInfo.TcbLevels[0].TcbStatus = "OutOfDate" wantErr = `TCB Status is not "UpToDate", found "OutOfDate"` if err := checkTcbInfoTcbStatus(tcbInfo, quote.GetTdQuoteBody(), ext); err == nil || err.Error() != wantErr { t.Errorf("TCB status expired: checkTcbInfoTcbStatus() = %v. Want error %v", err, wantErr) } } func TestNegativeCheckQeStatusV4(t *testing.T) { getter := testcases.TestGetter collateral := &Collateral{} if err := getQeIdentity(getter, collateral); err != nil { t.Fatal(err) } anyQuote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } quote, ok := anyQuote.(*pb.QuoteV4) if !ok { t.Fatal("quote is not a QuoteV4") } qeIdentity := collateral.QeIdentity.EnclaveIdentity qeReport := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData().GetQeReport() qeIdentity.TcbLevels[0].Tcb.Isvsvn = 10 wantErr := "unable to find latest status of TCB, it is now OutOfDate" if err := checkQeTcbStatus(qeIdentity.TcbLevels, qeReport.GetIsvSvn()); err == nil || err.Error() != wantErr { t.Errorf("No matching TCB level: verifyUsingQeIdentity() = %v. Want error %v", err, wantErr) } qeIdentity.TcbLevels[0].Tcb.Isvsvn = 0 qeIdentity.TcbLevels[0].TcbStatus = "OutOfDate" wantErr = `TCB Status is not "UpToDate", found "OutOfDate"` if err := checkQeTcbStatus(qeIdentity.TcbLevels, qeReport.GetIsvSvn()); err == nil || err.Error() != wantErr { t.Errorf("TCB status expired: verifyUsingQeIdentity() = %v. Want error %v", err, wantErr) } } func TestValidateCRL(t *testing.T) { getter := testcases.TestGetter quote, err := abi.QuoteToProto(testdata.RawQuote) if err != nil { t.Fatal(err) } chain, err := extractChainFromQuote(quote) if err != nil { t.Fatal(err) } ca := platformIssuerID collateral := &Collateral{} if err := getPckCrl(ca, getter, collateral); err != nil { t.Fatal(err) } if err := getQeIdentity(getter, collateral); err != nil { t.Fatal(err) } if err := getRootCrl(getter, collateral); err != nil { t.Fatal(err) } if err := validateCRL(collateral.RootCaCrl, chain.RootCertificate); err != nil { t.Error(err) } if err := validateCRL(collateral.PckCrl, chain.IntermediateCertificate); err != nil { t.Error(err) } } func TestNegativeRawQuoteVerifyWithCollateral(t *testing.T) { getter := testcases.TestGetter options := &Options{CheckRevocations: true, GetCollateral: true, Getter: getter, Now: currentTime} wantErr := "TDX TCB info reported by Intel PCS failed TCB status check: no matching TCB level found" // Due to updated SVN values in the sample response, it will result in TCB status failure, // when compared to the TD Quote Body's TeeTcbSvn value. if err := RawTdxQuote(testdata.RawQuote, options); err == nil || err.Error() != wantErr { t.Errorf("No matching TCB: RawTdxQuote() = %v. Want error %v", err, wantErr) } } func TestNegativeCheckRevocation(t *testing.T) { getter := testcases.TestGetter options := &Options{CheckRevocations: true, GetCollateral: false, Getter: getter} wantErr := "unable to check for certificate revocation as GetCollateral parameter in the options is set to false" if err := RawTdxQuote(testdata.RawQuote, options); err == nil || err.Error() != wantErr { t.Errorf("Check Revocation Without GetCollateral: RawTdxQuote() = %v. Want error %v", err, wantErr) } }