From 7388240943813093c033ed6d96b44c3991888c76 Mon Sep 17 00:00:00 2001 From: Otto Bittner Date: Thu, 22 Jun 2023 17:08:44 +0200 Subject: [PATCH] Revert "attestation: add SNP-based attestation for aws-sev-snp (#1916)" (#1957) This reverts commit c7d12055d1c8e5e6e58c16cd232a8c59a4b64ede. --- bazel/toolchains/go_module_deps.bzl | 5 +- cli/internal/cloudcmd/create.go | 4 +- cli/internal/cmd/upgradeapply.go | 1 - docs/docs/reference/cli.md | 2 +- go.mod | 4 +- go.sum | 4 +- internal/attestation/aws/snp/BUILD.bazel | 13 +- internal/attestation/aws/snp/errors.go | 48 --- internal/attestation/aws/snp/issuer.go | 52 +-- internal/attestation/aws/snp/issuer_test.go | 82 ++++- internal/attestation/aws/snp/snp.go | 4 - internal/attestation/aws/snp/validator.go | 212 +++--------- .../attestation/aws/snp/validator_test.go | 301 ++++-------------- internal/attestation/variant/variant.go | 2 +- internal/attestation/vtpm/BUILD.bazel | 3 - internal/attestation/vtpm/attestation.go | 46 --- internal/attestation/vtpm/attestation_test.go | 43 --- internal/config/attestation_test.go | 2 +- internal/config/azure.go | 3 +- internal/config/config.go | 33 +- internal/config/config_doc.go | 8 +- internal/config/config_test.go | 6 +- internal/constants/constants.go | 7 - 23 files changed, 239 insertions(+), 646 deletions(-) delete mode 100644 internal/attestation/aws/snp/errors.go diff --git a/bazel/toolchains/go_module_deps.bzl b/bazel/toolchains/go_module_deps.bzl index 555a786f7..3262aba2e 100644 --- a/bazel/toolchains/go_module_deps.bzl +++ b/bazel/toolchains/go_module_deps.bzl @@ -2769,9 +2769,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/google/go-sev-guest", - replace = "github.com/derpsteb/go-sev-guest", - sum = "h1:rqEp/ttS4sPC6dNwdiX0A9smWyyPxGqa/0sqJhXDzTg=", - version = "v0.0.0-20230612061930-77cc6c19fa1a", + sum = "h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2btFtk0=", + version = "v0.6.1", ) go_repository( name = "com_github_google_go_tpm", diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index a4fe8870b..64b5123eb 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -144,9 +144,7 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, opts Create IAMProfileControlPlane: opts.Config.Provider.AWS.IAMProfileControlPlane, IAMProfileWorkerNodes: opts.Config.Provider.AWS.IAMProfileWorkerNodes, Debug: opts.Config.IsDebugCluster(), - // We always want to use SNP machines. If the users decides to use NitroTPM attestation, - // they will at least have runtime encryption. - EnableSNP: true, + EnableSNP: opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AWSSEVSNP{}), } if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil { diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index 401de35a2..e0052d699 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -240,7 +240,6 @@ func parseTerraformUpgradeVars(cmd *cobra.Command, conf *config.Config, fetcher IAMProfileControlPlane: conf.Provider.AWS.IAMProfileControlPlane, IAMProfileWorkerNodes: conf.Provider.AWS.IAMProfileWorkerNodes, Debug: conf.IsDebugCluster(), - // TODO (AB#3235): decide how to handle EnableSNP during upgrades. } return targets, vars, nil case cloudprovider.Azure: diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 20f5fb15a..c8322273d 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -75,7 +75,7 @@ constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags] ### Options ``` - -a, --attestation string attestation variant to use {aws-nitro-tpm|aws-sev-snp|azure-sev-snp|azure-trustedlaunch|gcp-sev-es|qemu-vtpm}. If not specified, the default for the cloud provider is used + -a, --attestation string attestation variant to use {aws-sev-snp|aws-nitro-tpm|azure-sev-snp|azure-trustedlaunch|gcp-sev-es|qemu-vtpm}. If not specified, the default for the cloud provider is used -f, --file string path to output file, or '-' for stdout (default "constellation-conf.yaml") -h, --help help for generate -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR (default "v1.26") diff --git a/go.mod b/go.mod index afef89d6e..44ffd99a3 100644 --- a/go.mod +++ b/go.mod @@ -33,8 +33,6 @@ replace ( replace ( github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api => ./operators/constellation-node-operator/api - // We need to extend this PR and merge it to go back to upstream: https://github.com/google/go-sev-guest/pull/47 - github.com/google/go-sev-guest => github.com/derpsteb/go-sev-guest v0.0.0-20230612061930-77cc6c19fa1a github.com/google/go-tpm => github.com/thomasten/go-tpm v0.0.0-20230222180349-bb3cc5560299 github.com/google/go-tpm-tools => github.com/daniel-weisse/go-tpm-tools v0.0.0-20230612131025-c1ddd5ded590 ) @@ -231,7 +229,7 @@ require ( github.com/google/go-attestation v0.4.4-0.20221011162210-17f9c05652a9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-containerregistry v0.14.1-0.20230409045903-ed5c185df419 // indirect - github.com/google/go-sev-guest v0.6.1 + github.com/google/go-sev-guest v0.6.1 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/logger v1.1.1 // indirect diff --git a/go.sum b/go.sum index c3ad707b4..8e196e8ca 100644 --- a/go.sum +++ b/go.sum @@ -394,8 +394,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/derpsteb/go-sev-guest v0.0.0-20230612061930-77cc6c19fa1a h1:rqEp/ttS4sPC6dNwdiX0A9smWyyPxGqa/0sqJhXDzTg= -github.com/derpsteb/go-sev-guest v0.0.0-20230612061930-77cc6c19fa1a/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -701,6 +699,8 @@ github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOm github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/go-sev-guest v0.6.1 h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2btFtk0= +github.com/google/go-sev-guest v0.6.1/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q= github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= diff --git a/internal/attestation/aws/snp/BUILD.bazel b/internal/attestation/aws/snp/BUILD.bazel index dd726ba76..959be7151 100644 --- a/internal/attestation/aws/snp/BUILD.bazel +++ b/internal/attestation/aws/snp/BUILD.bazel @@ -4,7 +4,6 @@ load("//bazel/go:go_test.bzl", "go_test") go_library( name = "snp", srcs = [ - "errors.go", "issuer.go", "snp.go", "validator.go", @@ -16,9 +15,9 @@ go_library( "//internal/attestation/variant", "//internal/attestation/vtpm", "//internal/config", - "@com_github_google_go_sev_guest//abi", - "@com_github_google_go_sev_guest//client", - "@com_github_google_go_sev_guest//verify", + "@com_github_aws_aws_sdk_go_v2_config//:config", + "@com_github_aws_aws_sdk_go_v2_feature_ec2_imds//:imds", + "@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2", "@com_github_google_go_tpm//tpm2", "@com_github_google_go_tpm_tools//client", "@com_github_google_go_tpm_tools//proto/attest", @@ -31,7 +30,6 @@ go_test( "issuer_test.go", "validator_test.go", ], - data = glob(["testdata/**"]), embed = [":snp"], # keep gotags = select({ @@ -41,7 +39,10 @@ go_test( deps = [ "//internal/attestation/simulator", "//internal/attestation/vtpm", - "//internal/constants", + "@com_github_aws_aws_sdk_go_v2_feature_ec2_imds//:imds", + "@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2", + "@com_github_aws_aws_sdk_go_v2_service_ec2//types", + "@com_github_aws_smithy_go//middleware", "@com_github_google_go_tpm_tools//client", "@com_github_google_go_tpm_tools//proto/attest", "@com_github_stretchr_testify//assert", diff --git a/internal/attestation/aws/snp/errors.go b/internal/attestation/aws/snp/errors.go deleted file mode 100644 index 2960455f9..000000000 --- a/internal/attestation/aws/snp/errors.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package snp - -import "fmt" - -// decodeError is used to signal an error during decoding of a public key. -// It only wrapps an error. -type decodeError struct { - inner error -} - -// newDecodeError an error in a DecodeError. -func newDecodeError(err error) *decodeError { - return &decodeError{inner: err} -} - -func (e *decodeError) Error() string { - return fmt.Sprintf("error decoding public key: %v", e.inner) -} - -func (e *decodeError) Unwrap() error { - return e.inner -} - -// validationError is used to signal an invalid SNP report. -// It only wrapps an error. -// Used during testing to error conditions more precisely. -type validationError struct { - inner error -} - -// newValidationError wraps an error in a ValidationError. -func newValidationError(err error) *validationError { - return &validationError{inner: err} -} - -func (e *validationError) Error() string { - return e.inner.Error() -} - -func (e *validationError) Unwrap() error { - return e.inner -} diff --git a/internal/attestation/aws/snp/issuer.go b/internal/attestation/aws/snp/issuer.go index 0742f9a7e..067b309b5 100644 --- a/internal/attestation/aws/snp/issuer.go +++ b/internal/attestation/aws/snp/issuer.go @@ -8,17 +8,15 @@ package snp import ( "context" - "crypto/sha512" - "crypto/x509" "encoding/json" "fmt" "io" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/edgelesssys/constellation/v2/internal/attestation" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" - sevclient "github.com/google/go-sev-guest/client" "github.com/google/go-tpm-tools/client" tpmclient "github.com/google/go-tpm-tools/client" ) @@ -35,7 +33,7 @@ func NewIssuer(log attestation.Logger) *Issuer { Issuer: vtpm.NewIssuer( vtpm.OpenVTPM, getAttestationKey, - getInstanceInfo, + getInstanceInfo(imds.New(imds.Options{})), log, ), } @@ -51,44 +49,18 @@ func getAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) { return tpmAk, nil } -// getInstanceInfo generates an extended SNP report, i.e. the report and any loaded certificates. -// Report generation is triggered by sending ioctl syscalls to the SNP guest device, the AMD PSP generates the report. +// getInstanceInfo returns information about the current instance using the aws Metadata SDK. // The returned bytes will be written into the attestation document. -func getInstanceInfo(_ context.Context, tpm io.ReadWriteCloser, _ []byte) ([]byte, error) { - tpmAk, err := client.AttestationKeyRSA(tpm) - if err != nil { - return nil, fmt.Errorf("error creating RSA Endorsement key: %w", err) +func getInstanceInfo(client awsMetaData) func(context.Context, io.ReadWriteCloser, []byte) ([]byte, error) { + return func(ctx context.Context, _ io.ReadWriteCloser, _ []byte) ([]byte, error) { + ec2InstanceIdentityOutput, err := client.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{}) + if err != nil { + return nil, fmt.Errorf("fetching instance identity document: %w", err) + } + return json.Marshal(ec2InstanceIdentityOutput.InstanceIdentityDocument) } - - encoded, err := x509.MarshalPKIXPublicKey(tpmAk.PublicKey()) - if err != nil { - return nil, fmt.Errorf("marshalling public key: %w", err) - } - - akDigest := sha512.Sum512(encoded) - - device, err := sevclient.OpenDevice() - if err != nil { - return nil, fmt.Errorf("opening sev device: %w", err) - } - defer device.Close() - - report, certs, err := sevclient.GetRawExtendedReportAtVmpl(device, akDigest, 0) - if err != nil { - return nil, fmt.Errorf("getting extended report: %w", err) - } - - raw, err := json.Marshal(instanceInfo{Report: report, Certs: certs}) - if err != nil { - return nil, fmt.Errorf("marshalling instance info: %w", err) - } - - return raw, nil } -type instanceInfo struct { - // Report contains the marshalled AMD SEV-SNP Report. - Report []byte - // Certs contains the PEM encoded VLEK and ASK certificates, queried from the AMD PSP of the issuing party. - Certs []byte +type awsMetaData interface { + GetInstanceIdentityDocument(context.Context, *imds.GetInstanceIdentityDocumentInput, ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) } diff --git a/internal/attestation/aws/snp/issuer_test.go b/internal/attestation/aws/snp/issuer_test.go index 7f788ea22..29d17b5e1 100644 --- a/internal/attestation/aws/snp/issuer_test.go +++ b/internal/attestation/aws/snp/issuer_test.go @@ -7,8 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only package snp import ( + "context" + "errors" "testing" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/smithy-go/middleware" "github.com/edgelesssys/constellation/v2/internal/attestation/simulator" tpmclient "github.com/google/go-tpm-tools/client" "github.com/stretchr/testify/assert" @@ -23,7 +27,7 @@ func TestGetAttestationKey(t *testing.T) { require.NoError(err) defer tpm.Close() - // create the attestation key in RSA format + // create the attestation ket in RSA format tpmAk, err := tpmclient.AttestationKeyRSA(tpm) assert.NoError(err) assert.NotNil(tpmAk) @@ -36,3 +40,79 @@ func TestGetAttestationKey(t *testing.T) { // if everything worked fine, tpmAk and getAk are the same key assert.Equal(tpmAk, getAk) } + +func TestGetInstanceInfo(t *testing.T) { + testCases := map[string]struct { + client stubMetadataAPI + wantErr bool + }{ + "invalid region": { + client: stubMetadataAPI{ + instanceDoc: imds.InstanceIdentityDocument{ + Region: "invalid-region", + }, + instanceErr: errors.New("failed"), + }, + wantErr: true, + }, + "valid region": { + client: stubMetadataAPI{ + instanceDoc: imds.InstanceIdentityDocument{ + Region: "us-east-2", + }, + }, + }, + "invalid imageID": { + client: stubMetadataAPI{ + instanceDoc: imds.InstanceIdentityDocument{ + ImageID: "ami-fail", + }, + instanceErr: errors.New("failed"), + }, + wantErr: true, + }, + "valid imageID": { + client: stubMetadataAPI{ + instanceDoc: imds.InstanceIdentityDocument{ + ImageID: "ami-09e7c7f5617a47830", + }, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + tpm, err := simulator.OpenSimulatedTPM() + assert.NoError(err) + defer tpm.Close() + + instanceInfoFunc := getInstanceInfo(&tc.client) + assert.NotNil(instanceInfoFunc) + + info, err := instanceInfoFunc(context.Background(), tpm, nil) + if tc.wantErr { + assert.Error(err) + assert.Nil(info) + } else { + assert.Nil(err) + assert.NotNil(info) + } + }) + } +} + +type stubMetadataAPI struct { + instanceDoc imds.InstanceIdentityDocument + instanceErr error +} + +func (c *stubMetadataAPI) GetInstanceIdentityDocument(context.Context, *imds.GetInstanceIdentityDocumentInput, ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) { + output := &imds.InstanceIdentityDocument{} + + return &imds.GetInstanceIdentityDocumentOutput{ + InstanceIdentityDocument: *output, + ResultMetadata: middleware.Metadata{}, + }, c.instanceErr +} diff --git a/internal/attestation/aws/snp/snp.go b/internal/attestation/aws/snp/snp.go index de3ab3333..4085f50bb 100644 --- a/internal/attestation/aws/snp/snp.go +++ b/internal/attestation/aws/snp/snp.go @@ -36,10 +36,6 @@ Thus, the hypervisor is still included in the trusted computing base. This section explains abbreviations used in SNP implementation. - - Platform Security Processor (PSP) - - - Certificate Revocation List (CRL) - - Attestation Key (AK) - AMD Root Key (ARK) diff --git a/internal/attestation/aws/snp/validator.go b/internal/attestation/aws/snp/validator.go index 4982089cb..d1db3dfae 100644 --- a/internal/attestation/aws/snp/validator.go +++ b/internal/attestation/aws/snp/validator.go @@ -7,213 +7,95 @@ SPDX-License-Identifier: AGPL-3.0-only package snp import ( - "bytes" "context" "crypto" - "crypto/sha512" - "crypto/x509" "encoding/json" - "encoding/pem" - "errors" "fmt" - "io" - "net/http" + awsConfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/edgelesssys/constellation/v2/internal/attestation" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" "github.com/edgelesssys/constellation/v2/internal/config" - "github.com/google/go-sev-guest/abi" - "github.com/google/go-sev-guest/verify" "github.com/google/go-tpm-tools/proto/attest" "github.com/google/go-tpm/tpm2" ) // Validator for AWS TPM attestation. type Validator struct { - // Embed variant to identify the Validator using varaint.OID(). variant.AWSSEVSNP - // Embed validator to implement Validate method for aTLS handshake. *vtpm.Validator - // AMD root key. Root of trust for the ASK used during report validation. - ark *x509.Certificate - // kdsClient gets an ASK from somewhere. kdsClient is required for testing. - kdsClient askGetter - // reportValidator validates a SNP report. reportValidator is required for testing. - reportValidator snpReportValidator + getDescribeClient func(context.Context, string) (awsMetadataAPI, error) } // NewValidator create a new Validator structure and returns it. func NewValidator(cfg *config.AWSSEVSNP, log attestation.Logger) *Validator { - v := &Validator{ - ark: (*x509.Certificate)(&cfg.AMDRootKey), - kdsClient: kdsClient{http.DefaultClient}, - reportValidator: awsValidator{}, - } - + v := &Validator{} v.Validator = vtpm.NewValidator( cfg.Measurements, - v.getTrustedKey, - func(vtpm.AttestationDocument, *attest.MachineState) error { return nil }, + getTrustedKey, + v.tpmEnabled, log, ) + v.getDescribeClient = getEC2Client return v } // getTrustedKeys return the public area of the provides attestation key. -// Normally, the key should be verified here, but currently AWS does not provide means to do so. -func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error) { +// Normally, here the trust of this key should be verified, but currently AWS does not provide this feature. +func getTrustedKey(_ context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error) { // Copied from https://github.com/edgelesssys/constellation/blob/main/internal/attestation/qemu/validator.go pubArea, err := tpm2.DecodePublic(attDoc.Attestation.AkPub) if err != nil { - return nil, newDecodeError(err) - } - - pubKey, err := pubArea.Key() - if err != nil { - return nil, fmt.Errorf("getting public key: %w", err) - } - - akDigest, err := sha512sum(pubKey) - if err != nil { - return nil, fmt.Errorf("calculating hash of attestation key: %w", err) - } - - if err := v.reportValidator.validate(ctx, attDoc, v.kdsClient, v.ark, akDigest); err != nil { - return nil, fmt.Errorf("validating SNP report: %w", err) + return nil, err } return pubArea.Key() } -// sha512sum PEM-encodes a public key and calculates the SHA512 hash of the encoded key. -func sha512sum(key crypto.PublicKey) ([64]byte, error) { - pub, err := x509.MarshalPKIXPublicKey(key) +// tpmEnabled verifies if the virtual machine has the tpm2.0 feature enabled. +func (v *Validator) tpmEnabled(attestation vtpm.AttestationDocument, _ *attest.MachineState) error { + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-nitrotpm-support-on-ami.html + // 1. Get the vm's ami (from IdentiTyDocument.imageId) + // 2. Check the value of key "TpmSupport": {"Value": "v2.0"}" + ctx := context.Background() + + idDocument := imds.InstanceIdentityDocument{} + err := json.Unmarshal(attestation.InstanceInfo, &idDocument) if err != nil { - return [64]byte{}, fmt.Errorf("marshalling public key: %w", err) + return err } - return sha512.Sum512(pub), nil + imageID := idDocument.ImageID + + client, err := v.getDescribeClient(ctx, idDocument.Region) + if err != nil { + return err + } + // Currently, there seems to be a problem with retrieving image attributes directly. + // Alternatively, parse it from the general output. + imageOutput, err := client.DescribeImages(ctx, &ec2.DescribeImagesInput{ImageIds: []string{imageID}}) + if err != nil { + return err + } + + if imageOutput.Images[0].TpmSupport == "v2.0" { + return nil + } + + return fmt.Errorf("iam image %s does not support TPM v2.0", imageID) } -// Validate a given SNP report. -type snpReportValidator interface { - validate(ctx context.Context, attestation vtpm.AttestationDocument, kdsClient askGetter, ark *x509.Certificate, ak [64]byte) error +func getEC2Client(ctx context.Context, region string) (awsMetadataAPI, error) { + client, err := awsConfig.LoadDefaultConfig(ctx, awsConfig.WithRegion(region)) + if err != nil { + return nil, err + } + return ec2.NewFromConfig(client), nil } -// Validation logic for the AWS SNP implementation. -type awsValidator struct{} - -// validate the report by checking if it has a valid VLEK signature. -// The certificate chain ARK -> ASK -> VLEK is also validated. -// Checks that the report's userData matches the connection's userData. -func (awsValidator) validate(ctx context.Context, attestation vtpm.AttestationDocument, kdsClient askGetter, ark *x509.Certificate, akDigest [64]byte) error { - var info instanceInfo - if err := json.Unmarshal(attestation.InstanceInfo, &info); err != nil { - return newValidationError(fmt.Errorf("unmarshalling instance info: %w", err)) - } - - vlek, err := getVLEK(info.Certs) - if err != nil { - return newValidationError(fmt.Errorf("parsing certificates from certtable %x: %w", info.Certs, err)) - } - - ask, err := kdsClient.getASK(ctx) - if err != nil { - return newValidationError(fmt.Errorf("getting ASK: %w", err)) - } - - if err := ask.CheckSignatureFrom(ark); err != nil { - return newValidationError(fmt.Errorf("verifying ASK signature: %w", err)) - } - if err := vlek.CheckSignatureFrom(ask); err != nil { - return newValidationError(fmt.Errorf("verifying VLEK signature: %w", err)) - } - - if err := verify.SnpReportSignature(info.Report, vlek); err != nil { - return newValidationError(fmt.Errorf("verifying snp report signature: %w", err)) - } - - report, err := abi.ReportToProto(info.Report) - if err != nil { - return newValidationError(fmt.Errorf("unmarshalling SNP report: %w", err)) - } - - if !bytes.Equal(report.GetReportData(), akDigest[:]) { - return newValidationError(errors.New("SNP report and attestation statement contain mismatching attestation keys")) - } - - return nil -} - -// getVLEK parses the certificate table included in an extended SNP report -// and returns the VLEK certificate. -func getVLEK(certs []byte) (vlek *x509.Certificate, err error) { - certTable := abi.CertTable{} - if err = certTable.Unmarshal(certs); err != nil { - return nil, fmt.Errorf("unmarshalling SNP certificate table: %v", err) - } - - vlekRaw, err := certTable.GetByGUIDString(abi.VlekGUID) - if err != nil { - return nil, fmt.Errorf("getting VLEK certificate: %v", err) - } - - vlek, err = x509.ParseCertificate(vlekRaw) - if err != nil { - return nil, fmt.Errorf("parsing certificate: %w", err) - } - - return -} - -// Query the AMD key distribution service for an AMD signing key. -type kdsClient struct { - httpClient -} - -type httpClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// getASK requests the current certificate chain from the AMD KDS API and returns the ASK. -// There is no information on how to handle CRLs in the official AMD docs. -// Once github.com/google/go-sev-guest adds support to check CRLs for VLEK-based certificate chains -// we can check CRLs here. -func (k kdsClient) getASK(ctx context.Context) (*x509.Certificate, error) { - // If there are multiple CPU generations (and with that different API paths to call) in the future, - // we can select the correct path to call based on the information contained in the SNP report. - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://kdsintf.amd.com/vlek/v1/Milan/cert_chain", nil) - if err != nil { - return nil, fmt.Errorf("creating request: %w", err) - } - - resp, err := k.Do(req) - if err != nil { - return nil, fmt.Errorf("requesting ASK: %w", err) - } - defer resp.Body.Close() - - pemChain, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("reading response body: %w", err) - } - - // certificate chain starts with ASK. We hardcode the ARK, so ignore that. - decodedASK, _ := pem.Decode(pemChain) - if decodedASK == nil { - return nil, errors.New("no PEM data found") - } - - ask, err := x509.ParseCertificate(decodedASK.Bytes) - if err != nil { - return nil, fmt.Errorf("parsing ASK: %w", err) - } - - return ask, nil -} - -// askGetter gets an ASK from somewhere. -type askGetter interface { - getASK(ctx context.Context) (*x509.Certificate, error) +type awsMetadataAPI interface { + DescribeImages(ctx context.Context, params *ec2.DescribeImagesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error) } diff --git a/internal/attestation/aws/snp/validator_test.go b/internal/attestation/aws/snp/validator_test.go index a4656a990..0eeb826e3 100644 --- a/internal/attestation/aws/snp/validator_test.go +++ b/internal/attestation/aws/snp/validator_test.go @@ -7,110 +7,41 @@ SPDX-License-Identifier: AGPL-3.0-only package snp import ( - "bytes" "context" - "crypto" - "crypto/x509" - "encoding/hex" "encoding/json" - "encoding/pem" - "fmt" - "io" - "net/http" - "strings" + "errors" "testing" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" - "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/google/go-tpm-tools/proto/attest" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -const ( - // askPEM is a PEM encoded AMD signing key. - askPEM = `-----BEGIN CERTIFICATE----- -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 -0YPk ------END CERTIFICATE----- -` - - // certTableValid is a valid cert table from an extended SNP report. It includes a valid VLEK. - certTableValid = "a8074bc2a25a483eaae639c045a0b8a1300000002b05000000000000000000000000000000000000000000000000000030820527308202d6a003020102020100304606092a864886f70d01010a3039a00f300d06096086480165030402020500a11c301a06092a864886f70d010108300d06096086480165030402020500a203020130a30302010130818031143012060355040b0c0b456e67696e656572696e67310b30090603550406130255533114301206035504070c0b53616e746120436c617261310b300906035504080c024341311f301d060355040a0c16416476616e636564204d6963726f20446576696365733117301506035504030c0e5345562d564c454b2d4d696c616e301e170d3233303432303138333031365a170d3234303432303138333031365a307a31143012060355040b0c0b456e67696e656572696e67310b30090603550406130255533114301206035504070c0b53616e746120436c617261310b300906035504080c024341311f301d060355040a0c16416476616e636564204d6963726f20446576696365733111300f06035504030c085345562d564c454b3076301006072a8648ce3d020106052b81040022036200049c1a9fd526e4f0abfb3b70bc53136ee75c2f6ffc38fee47790e4e3f1f13b08009db13f086c94f74b13f89c70b3b9d9cb296f1834114904754a3d844a3d7ddab170800912d7d8407e155b389b580ea39f108ce68b56d3321bdfc3414324a852eea381ec3081e9301006092b060104019c7801010403020100301406092b060104019c780102040716054d696c616e3011060a2b060104019c7801030104030201033011060a2b060104019c7801030204030201003011060a2b060104019c7801030404030201003011060a2b060104019c7801030504030201003011060a2b060104019c7801030604030201003011060a2b060104019c7801030704030201003011060a2b060104019c78010303040302010a3011060a2b060104019c780103080403020173302706092b060104019c780105041a1618434e3d63632d746573742e616d617a6f6e6177732e636f6d304606092a864886f70d01010a3039a00f300d06096086480165030402020500a11c301a06092a864886f70d010108300d06096086480165030402020500a203020130a30302010103820201002f5dc1b2c04023b90ef20ed2d791705407f6cbecb5ba9a2fa4354f1bfb4806e7bff976a5f6a2474c29a8e04a898a6a699dba12b0be88bbb4b713696b1e54c6cd946d17d9890ebd0ec68b3446f9f093149e1b57c4470e70ebdb911bb51d8a6f4678e710ca932fa8ed736a209d44656ffaba4447ee2e42b91a787c6049b94dbc2b31fe57f782c06894c9c2b33406e90f329d022c4ef5ed47061f8956d04c28bf47a79dbdb7f08e35a478589dba5cedbd66d3259ef86aecec528ea4889870f18bf900477146338d42749fbf0a6f245326998766cf1bd8449f434a00151ba3c4aa12812032b7c5bac2ac908cd9b9006749257a634356ca92c2a47edf728979174e9fe096ed65137f069bc8866591537b2ff0f8a6527963dfd83a18ea81ee13a3174ae8c5d200bddc8cd2813430e99048175ffc0b9614364f4d0c1278412f47e8dec380196fc6bee27bc8037bfc642ee4b100f11f6ce259b58fb908db2a3cf64f8460bb837aa0f4caaebb7bed22ea85d9984d2b9ccaca736da4bde2cbb1273e5086f63432206274e87adca8030a6a61640d396d2f301b4e29731da2c2ae3b9899bc515e7d1213e7033baec8da466d7b15489746310df8727b55feda3de413b65a1ab6336312ecb59088955533d304caa118bdd24cfdf1c1b15fcfda42cb4c1d86091e1949a6b79b5e8a65aa9ecdc3a5cd2a69ff1970317c11f074fc85fd630d7b34ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - // hashValid is the AK hash of the public key embedded in 'reportValid'. - hashValid = "44a93ab043ad14ece9bfa97305d95302c9cc6ed95e17efaf7348ed7a7603e1ca89d12758e089d2abcf5a4dd16a99e3cb4cba8f0b8e8cb8eac3e926f1d2b5cfec" - // reportValid is a valid SNP report. - reportValid = "02000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000300000000000ace0300000000000000040000000000000044a93ab043ad14ece9bfa97305d95302c9cc6ed95e17efaf7348ed7a7603e1ca89d12758e089d2abcf5a4dd16a99e3cb4cba8f0b8e8cb8eac3e926f1d2b5cfecc2c84b9364fc9f0f54b04534768c860c6e0e386ad98b96e8b98eca46ac8971d05c531ba48373f054c880cfd1f4a0a84e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008c5d6770df734a203cd061a3698e702caed25e7f744dc060eb9dcba0f2e4bdb2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300000000000a73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000a7301360100013601000300000000000a73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b9853dac65f127574c6a578c11885e1887d4c7ae446237d4273715dd8c05cfe4bd49facc1392f2ca7354c8f0d34d65500000000000000000000000000000000000000000000000004013481e9c6a6bb112818aeba3bd178d788dedf62600b8c7892a8d3df4d880265010e7d833201156364a001e62f47b570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - // reportInvalid = reportValid[0x2A0]+1. 673. character in the below string. That address is the start of the signature. - reportInvalid = "02000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000300000000000ace0300000000000000040000000000000044a93ab043ad14ece9bfa97305d95302c9cc6ed95e17efaf7348ed7a7603e1ca89d12758e089d2abcf5a4dd16a99e3cb4cba8f0b8e8cb8eac3e926f1d2b5cfecc2c84b9364fc9f0f54b04534768c860c6e0e386ad98b96e8b98eca46ac8971d05c531ba48373f054c880cfd1f4a0a84e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008c5d6770df734a203cd061a3698e702cbed25e7f744dc060eb9dcba0f2e4bdb2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300000000000a73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000a7301360100013601000300000000000a73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b9853dac65f127574c6a578c11885e1887d4c7ae446237d4273715dd8c05cfe4bd49facc1392f2ca7354c8f0d34d65500000000000000000000000000000000000000000000000004013481e9c6a6bb112818aeba3bd178d788dedf62600b8c7892a8d3df4d880265010e7d833201156364a001e62f47b570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" -) - -func TestGetTrustedKey(t *testing.T) { - validator := &Validator{ark: nil, kdsClient: nil, reportValidator: stubawsValidator{}} +func TestGeTrustedKey(t *testing.T) { testCases := map[string]struct { - akPub []byte - info []byte - wantErr bool - assertCorrectError func(error) + akPub []byte + info []byte + wantErr bool }{ - "null byte docs": { + "nul byte docs": { akPub: []byte{0x00, 0x00, 0x00, 0x00}, info: []byte{0x00, 0x00, 0x00, 0x00}, wantErr: true, - assertCorrectError: func(err error) { - target := &decodeError{} - assert.ErrorAs(t, err, &target) - }, }, "nil": { akPub: nil, info: nil, wantErr: true, - assertCorrectError: func(err error) { - target := &decodeError{} - assert.ErrorAs(t, err, &target) - }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) - out, err := validator.getTrustedKey( + out, err := getTrustedKey( context.Background(), vtpm.AttestationDocument{ Attestation: &attest.Attestation{ @@ -123,7 +54,6 @@ func TestGetTrustedKey(t *testing.T) { if tc.wantErr { assert.Error(err) - tc.assertCorrectError(err) } else { assert.NoError(err) } @@ -133,16 +63,47 @@ func TestGetTrustedKey(t *testing.T) { } } -func TestGetASK(t *testing.T) { +func TestTpmEnabled(t *testing.T) { + idDocNoTPM := imds.InstanceIdentityDocument{ + ImageID: "ami-tpm-disabled", + } + userDataNoTPM, _ := json.Marshal(idDocNoTPM) + attDocNoTPM := vtpm.AttestationDocument{ + InstanceInfo: userDataNoTPM, + } + + idDocTPM := imds.InstanceIdentityDocument{ + ImageID: "ami-tpm-enabled", + } + userDataTPM, _ := json.Marshal(idDocTPM) + attDocTPM := vtpm.AttestationDocument{ + InstanceInfo: userDataTPM, + } + testCases := map[string]struct { - client askGetter + attDoc vtpm.AttestationDocument + awsAPI awsMetadataAPI wantErr bool }{ - "success": { - client: kdsClient{stubClient{ask: []byte(askPEM)}}, + "ami with tpm": { + attDoc: attDocNoTPM, + awsAPI: &stubDescribeAPI{describeImagesTPMSupport: "v2.0"}, }, - "no cert": { - client: kdsClient{stubClient{ask: []byte{}}}, + "ami without tpm": { + attDoc: attDocTPM, + awsAPI: &stubDescribeAPI{describeImagesTPMSupport: "v1.0"}, + wantErr: true, + }, + "ami undefined": { + attDoc: vtpm.AttestationDocument{}, + awsAPI: &stubDescribeAPI{describeImagesErr: errors.New("failed")}, + wantErr: true, + }, + "invalid json instanceIdentityDocument": { + attDoc: vtpm.AttestationDocument{ + UserData: []byte("{invalid}"), + }, + awsAPI: &stubDescribeAPI{describeImagesErr: errors.New("failed")}, wantErr: true, }, } @@ -151,167 +112,35 @@ func TestGetASK(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) - ask, err := tc.client.getASK(context.Background()) + v := Validator{ + getDescribeClient: func(context.Context, string) (awsMetadataAPI, error) { + return tc.awsAPI, nil + }, + } + err := v.tpmEnabled(tc.attDoc, nil) if tc.wantErr { assert.Error(err) } else { - assert.NoError(err) - assert.NotNil(ask) + assert.Nil(err) } }) } } -// TestValidateSNPReport has to setup the following to run ValidateSNPReport: -// - parse ARK certificate from constants.go. -// - parse cached ASK certificate. -// - parse cached SNP report. -// - parse cached AK hash. Hash and SNP report have to match. -// - parse cache VLEK cert. -func TestValidateSNPReport(t *testing.T) { - ark, err := loadARK() - require.NoError(t, err) +type stubDescribeAPI struct { + describeImagesErr error + describeImagesTPMSupport string +} - testCases := map[string]struct { - ak string - client kdsClient - report string - wantErr bool - }{ - "success": { - ak: hashValid, - client: kdsClient{stubClient{ask: []byte(askPEM)}}, - report: reportValid, - }, - "fail": { - ak: hashValid, - client: kdsClient{stubClient{ask: []byte(askPEM)}}, - report: reportInvalid, - wantErr: true, +func (a *stubDescribeAPI) DescribeImages( + _ context.Context, _ *ec2.DescribeImagesInput, _ ...func(*ec2.Options), +) (*ec2.DescribeImagesOutput, error) { + output := &ec2.DescribeImagesOutput{ + Images: []types.Image{ + {TpmSupport: types.TpmSupportValues(a.describeImagesTPMSupport)}, }, } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - info, err := loadInstanceInfo(tc.report, certTableValid) - require.NoError(err) - - hash, err := hex.DecodeString(tc.ak) - require.NoError(err) - - v := awsValidator{} - err = v.validate(context.Background(), vtpm.AttestationDocument{InstanceInfo: info}, tc.client, ark, [64]byte(hash)) - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - } - }) - } -} - -func TestSha512sum(t *testing.T) { - testCases := map[string]struct { - key string - hash string - match bool - }{ - "success": { - // Generated using: rsa.GenerateKey(rand.Reader, 1024). - key: "30819f300d06092a864886f70d010101050003818d0030818902818100d4b2f072a32fa98456eb7f5938e2ff361fb64d698ea91e003d34bfc5374b814c16ba9ae3ec392ef6d48cf79b63067e338aa941219a7bcdf18aa43cd38bbe5567504838a3b1dca482035458853c5a171709dfae9df551815010bdfbc6df733cde84c4f7a5b0591d9cda9db087fb411ee3e2a4f19ad50c8331712ecdc5dd7ce34b0203010001", - hash: "2d6fe5ec59d7240b8a4c27c2ff27ba1071105fa50d45543768fcbabf9ee3cb8f8fa0afa51e08e053af30f6d11066ebfd47e75bda5ccc085c115d7e1896f3c62f", - match: true, - }, - "mismatching hash": { - key: "30819f300d06092a864886f70d010101050003818d0030818902818100d4b2f072a32fa98456eb7f5938e2ff361fb64d698ea91e003d34bfc5374b814c16ba9ae3ec392ef6d48cf79b63067e338aa941219a7bcdf18aa43cd38bbe5567504838a3b1dca482035458853c5a171709dfae9df551815010bdfbc6df733cde84c4f7a5b0591d9cda9db087fb411ee3e2a4f19ad50c8331712ecdc5dd7ce34b0203010001", - hash: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - match: false, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - newKey, err := loadKeyFromHex(tc.key) - require.NoError(err) - - // Function under test: - hash, err := sha512sum(newKey) - assert.NoError(err) - - expected, err := hex.DecodeString(tc.hash) - require.NoError(err) - - if tc.match { - assert.True(bytes.Equal(expected, hash[:]), fmt.Sprintf("expected hash %x, got %x", expected, hash)) - } else { - assert.False(bytes.Equal(expected, hash[:]), fmt.Sprintf("expected mismatching hashes, got %x", hash)) - } - }) - } -} - -func loadKeyFromHex(key string) (crypto.PublicKey, error) { - decoded, err := hex.DecodeString(key) - if err != nil { - return nil, err - } - - return x509.ParsePKIXPublicKey(decoded) -} - -// loadCachedCertChain loads a valid ARK and ASK from the testdata folder. -func loadARK() (*x509.Certificate, error) { - // Replacement is needed as the newline chars are not interpreted due to the backticks. Backticks are required for config formatting. - tmp := strings.ReplaceAll(constants.AMDRootKey, "\\n", "\n") - block, _ := pem.Decode([]byte(tmp)) - ark, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err - } - - return ark, nil -} - -// loadInstanceInfo loads a valid SNP report and VLEK cert from the testdata folder. -func loadInstanceInfo(report, certs string) ([]byte, error) { - reportDec, err := hex.DecodeString(report) - if err != nil { - return nil, err - } - - certsDec, err := hex.DecodeString(certs) - if err != nil { - return nil, err - } - - info := instanceInfo{Report: reportDec, Certs: certsDec} - infoRaw, err := json.Marshal(info) - if err != nil { - return nil, err - } - - return infoRaw, nil -} - -type stubClient struct { - ask []byte -} - -func (s stubClient) Do(*http.Request) (*http.Response, error) { - return &http.Response{ - Body: io.NopCloser(bytes.NewReader(s.ask)), - }, nil -} - -type stubawsValidator struct{} - -func (stubawsValidator) validate(context.Context, vtpm.AttestationDocument, askGetter, *x509.Certificate, [64]byte) error { - return nil + return output, a.describeImagesErr } diff --git a/internal/attestation/variant/variant.go b/internal/attestation/variant/variant.go index 5c8474bdb..3ab672257 100644 --- a/internal/attestation/variant/variant.go +++ b/internal/attestation/variant/variant.go @@ -51,7 +51,7 @@ const ( ) var providerAttestationMapping = map[cloudprovider.Provider][]Variant{ - cloudprovider.AWS: {AWSNitroTPM{}, AWSSEVSNP{}}, + cloudprovider.AWS: {AWSSEVSNP{}, AWSNitroTPM{}}, cloudprovider.Azure: {AzureSEVSNP{}, AzureTrustedLaunch{}}, cloudprovider.GCP: {GCPSEVES{}}, cloudprovider.QEMU: {QEMUVTPM{}}, diff --git a/internal/attestation/vtpm/BUILD.bazel b/internal/attestation/vtpm/BUILD.bazel index a51a2613e..cd27aeb56 100644 --- a/internal/attestation/vtpm/BUILD.bazel +++ b/internal/attestation/vtpm/BUILD.bazel @@ -17,7 +17,6 @@ go_library( "@com_github_google_go_tpm_tools//proto/attest", "@com_github_google_go_tpm_tools//proto/tpm", "@com_github_google_go_tpm_tools//server", - "@org_golang_google_protobuf//encoding/protojson", ], ) @@ -27,7 +26,6 @@ go_test( "attestation_test.go", "vtpm_test.go", ], - data = glob(["testdata/**"]), embed = [":vtpm"], # keep gotags = select({ @@ -39,7 +37,6 @@ go_test( "//internal/attestation/measurements", "//internal/attestation/simulator", "//internal/logger", - "@com_github_google_go_sev_guest//proto/sevsnp", "@com_github_google_go_tpm//tpm2", "@com_github_google_go_tpm_tools//client", "@com_github_google_go_tpm_tools//proto/attest", diff --git a/internal/attestation/vtpm/attestation.go b/internal/attestation/vtpm/attestation.go index 0bcff3c78..ef4c35092 100644 --- a/internal/attestation/vtpm/attestation.go +++ b/internal/attestation/vtpm/attestation.go @@ -19,7 +19,6 @@ import ( tpmProto "github.com/google/go-tpm-tools/proto/tpm" tpmServer "github.com/google/go-tpm-tools/server" "github.com/google/go-tpm/tpm2" - "google.golang.org/protobuf/encoding/protojson" "github.com/edgelesssys/constellation/v2/internal/attestation" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" @@ -77,51 +76,6 @@ type AttestationDocument struct { UserData []byte } -type auxAttestationDocument struct { - // Attestation contains the TPM event log, PCR values and quotes, and public key of the key used to sign the attestation. - Attestation json.RawMessage - // InstanceInfo is used to verify the provided public key. - InstanceInfo []byte - // arbitrary data, quoted by the TPM. - UserData []byte -} - -// MarshalJSON is needed as the proto definition of attest.Attestation uses protobuf's oneof feature. -// That feature is not handled correctly by encoding/json. -func (a AttestationDocument) MarshalJSON() ([]byte, error) { - attestation, err := protojson.Marshal(a.Attestation) - if err != nil { - return nil, fmt.Errorf("marshaling attestation: %w", err) - } - - aux := auxAttestationDocument{ - Attestation: attestation, - InstanceInfo: a.InstanceInfo, - UserData: a.UserData, - } - return json.Marshal(aux) -} - -// UnmarshalJSON is needed as the proto definition of attest.Attestation uses protobuf's oneof feature. -// That feature is not handled correctly by encoding/json. -func (a *AttestationDocument) UnmarshalJSON(b []byte) error { - aux := auxAttestationDocument{} - if err := json.Unmarshal(b, &aux); err != nil { - return fmt.Errorf("unmarshaling AttestationDocument: %w", err) - } - - attestation := &attest.Attestation{} - if err := protojson.Unmarshal(aux.Attestation, attestation); err != nil { - return fmt.Errorf("unmarshaling attestation: %w", err) - } - - a.Attestation = attestation - a.InstanceInfo = aux.InstanceInfo - a.UserData = aux.UserData - - return nil -} - // Issuer handles issuing of TPM based attestation documents. type Issuer struct { openTPM TPMOpenFunc diff --git a/internal/attestation/vtpm/attestation_test.go b/internal/attestation/vtpm/attestation_test.go index 59276dbbb..4a0327879 100644 --- a/internal/attestation/vtpm/attestation_test.go +++ b/internal/attestation/vtpm/attestation_test.go @@ -15,7 +15,6 @@ import ( "io" "testing" - "github.com/google/go-sev-guest/proto/sevsnp" tpmclient "github.com/google/go-tpm-tools/client" "github.com/google/go-tpm-tools/proto/attest" "github.com/google/go-tpm-tools/proto/tpm" @@ -30,9 +29,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/logger" ) -// attDocRaw is a valid attestation document used for smoke-testing the SNP report unmarshalling. -const attDocRaw = `{"Attestation":{"akPub":"AAEACwAFAHIAAAAQABQACwgAAAAAAAEArxfXJUX03DMFeQYwO8DS5fYFTdOdUOC8lapZGkl+G9MMjSPCXQJEFas3qOiSohK9nwwVFtVPFuaI16Mwc5naVCWBD1eFAz5p4m3I6C2M5TK64QHIMUT42fc+YB8xy5f6e52P6nP7OQlWJyZL1g8ItfeHMhLxPppJdqrVfCwpZwEaaLxkGPnhoeSFq17SzuuX388LqDq+z/zEdkb5eq060KsVRXZiAKSHZ2b1X9TU24TRD9gbHzeqTgmsSv+ejdBDkIvUCGffSOPWboQHIFbaTB/utrDx9Qanv+SvLN8TNccs1cjPVlm7ibAgHbnmj323SQZgl3P0hVg+aB9C/gwfjQ==","quotes":[{"quote":"/1RDR4AYACIACzusvJ74XCdH29qji0/iiqhEcwGq5LcXk9NZA/Lt+/7mACCzxSSaIYIUEzJi9alDVwiQ7KmfDyz27KA29yoPJhiWlgAAAAAAB+nEkSsHaXVdPBEBwZB6wPh6pj0AAAABAAsD////ACB1BKHP6t92CX0ty/1MPNBlWYyp3bzhx9l9/BaqBpgpzg==","rawSig":"ABQACwEAoiod+IlGvq2n1AFwII9qGtLtV94MO/U2aJgrrTUAh3CNqeesJVLLEu+lDGdOxxvIVEhwxREGqsk4iP1cQNaoeDgtN9pfg8sgTqb2lyRcGbPD7fEbg8RyMCpNuRLVh8mOe53xGVJsFsK2p7kw7DfhQmxiWU1XCCdnhwYEaY8FeHhdwanc6o2ACkcKxDy/kDaf9bfAXb9/mcU95VKOpHI/ZBvqJtCO9KgwoAZQTumSOQ4MnuneksIZq8+eTR36l176WNdV+jD1GvWi+nu7JB4SMQnh+nDH02T91uZbIqaJyOiqo95jdYBnaOSWrWtTPR6RN3uUK1bERSDXVPQzDGhBdg==","pcrs":{"hash":"SHA256","pcrs":{"0":"ewaMDDrCmv4mQTRTa5vibx1MzVdbiNPDzqvzasmcAng=","1":"lBcKsylvKkzBRRjDZ5PtEKssNkrIE01ryiBYZfNwWpM=","2":"PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=","3":"PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=","4":"m4kxK6nKfuCQwYbM+5DvQRkoItFxOyQMQtPt+9boeOY=","5":"n53xb43ld0aeGQTkItQbwd8MF6x3sI3SUR/nnS7LBPs=","6":"PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=","7":"+3Hl5Vzvup4rOW0XYE3g/m4YQadnWIVqEggz460cQKM=","8":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","9":"3YJHcjoTpTj4YP1iU5Y9WudkwroKc+kUoM7XgESB11c=","10":"hVfaAbz7VHG2kLTWyrspqJbK8IIJK8wGVVmgD1atCbM=","11":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","12":"nmfmHDL9KBUCtI3c4X7+39YfHGRd3MNegdXKW068gTk=","13":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","14":"18TMf/eTMCLwE+A73uh1uRcgtbhs8XU8rYMPleeRkm8=","15":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","16":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","17":"//////////////////////////////////////////8=","18":"//////////////////////////////////////////8=","19":"//////////////////////////////////////////8=","20":"//////////////////////////////////////////8=","21":"//////////////////////////////////////////8=","22":"//////////////////////////////////////////8=","23":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="}}},{"quote":"/1RDR4AYACIACzusvJ74XCdH29qji0/iiqhEcwGq5LcXk9NZA/Lt+/7mACCzxSSaIYIUEzJi9alDVwiQ7KmfDyz27KA29yoPJhiWlgAAAAAAB+q6kSsHaXVdPBEBwZB6wPh6pj0AAAABAAwD////ACBDyU6rmoq/MGRuXOZtSN0zMwVt54NMGEZzpEiKvu8UFQ==","rawSig":"ABQACwEALEJYI9+t9DxhC8OpjDsN3z7lko27sMOZiLBzUN2nad7GVpwAZRf77Hq7T5NuuXXVCbXeIXPPFAutTsjipXjK4a92Ogr/NSQz+Xy0Az991BMiv2WlPMB+0bs/UsPBIEWJaL2CgnBf3Uaq2esfRJBkBRnigCe+dLX9w6GgAOasEWhKXy8oyDn5e/XItbOg+Ug2ufLlxWHOShzXdX66XNx7NldxnB0CPhJY24xKyMmgJRQHB367wtj1byx6WE3BK3HfcFMUTndRgtY9XTdteynd81pUBgxfdu0/KDR8OlEDzM6wDwXENTBdGYJjerPou1IGAfvdP3g+mnOQlCUhWEMDlw==","pcrs":{"hash":"SHA384","pcrs":{"0":"3EZiPRyTft1GbeUgzqE6DytXO5QIT22qUhbtcNw/B19go7VVM4Ga69lMORmD7A2y","1":"7ojUJQGNzJJCSgCIqACM0HMZA+E+P6pfC6givgi+juXWdCNtMx13lRLnibuk7HGy","2":"UYkjsPlV0I2gd8lqq6Uiud7O3mHFmc6mxBiJz76krk1QUp2W/k0a/a+2Xn+VvyPE","3":"UYkjsPlV0I2gd8lqq6Uiud7O3mHFmc6mxBiJz76krk1QUp2W/k0a/a+2Xn+VvyPE","4":"aJMP6E2BOfqfg0kIzsPPHgnbUjHGW1BWrsKvt9iRkEpQFc3xVmJNLa1j2X/emu5v","5":"auzDenRwYRJhPgsawrCdR629m7E5UD6Aln/3GH/dbjAGIOKrM0k7srscgA6IAPRf","6":"UYkjsPlV0I2gd8lqq6Uiud7O3mHFmc6mxBiJz76krk1QUp2W/k0a/a+2Xn+VvyPE","7":"mjLHXDKNMkEb8nU7Fw55cJAQ2kFBNf0rh+8DVn8lZK3qaBUpkOnCDVTbNHEZcrIx","8":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","9":"HXzMvbSpA6bsC+1L1KpXtdZl4Qf7h+PYjsoD3fCx2ncJLhIZgL9iWxrNNJ+7UVGK","10":"lYr8xNLTD0ckvTl6J/W3NIp/A3LPOfTpnW8J2JWsmpbk8th874Pl4/xxFmmmdUnk","11":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","12":"JJkqFH7ieQaqyzLKE8XXXm0oIx8qqKHHHJtYGhN4SJmBrQkAhOx0kTKVhIMXPCIN","13":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","14":"AT/OjGKKHa+3e6+vrBwwt+DVtZc9J2z3C352VGKrMlBG1wpZD2uTMDUnWvmLO8xH","15":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","16":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","17":"////////////////////////////////////////////////////////////////","18":"////////////////////////////////////////////////////////////////","19":"////////////////////////////////////////////////////////////////","20":"////////////////////////////////////////////////////////////////","21":"////////////////////////////////////////////////////////////////","22":"////////////////////////////////////////////////////////////////","23":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}}},{"quote":"/1RDR4AYACIACzusvJ74XCdH29qji0/iiqhEcwGq5LcXk9NZA/Lt+/7mACCzxSSaIYIUEzJi9alDVwiQ7KmfDyz27KA29yoPJhiWlgAAAAAAB+vdkSsHaXVdPBEBwZB6wPh6pj0AAAABAA0D////ACACIRaJ7z1XVZ0G/fpsOTnrQhBiEIDU/JPK8hVE2ou6YA==","rawSig":"ABQACwEAQ+fnBPHTkwwEDI6miPtEQXLMn271zNdeGTcWDBQIets1NJaSYhgGInwTpdEDa7dTqXViHjHCLq0tctOMBTkYRLl23Q9Tpq/ZMsDllF/121KmFypUl4kiS4aDQOQodtuLcnYER+a/aXtuGVVMgojcIYKGhU3SnGDBQY3PwmNAFZVbNO0Qoa1pT2Nc+KTID9MQQxikESAWslMvpK/RJmbyvyb8dYIzhOJcTV/jEzwUVBbEb8Sfaic7vLWFS+ZRLbpft/CQh4iNLgpDHcd8Q4Fe4gykctaIWYsjpjRvXKY06UsTZuK8OJ/IUK5sUAlFBfAgiHh43AO/9nyOMUREporZlg==","pcrs":{"hash":"SHA512","pcrs":{"0":"ZAHmBFpxeI+azyLt0KcsjO9Zgw3YlHjC6iWTNyGm17jIFtZc/cQRv42HxdTB/W0Q/eMjhC6DIx4asXvfmO6gcA==","1":"ox5+tdL16TO8mB60KNMmTW3uCYp1ya0zzbYFf2uc9m5zt7NLuAXMJcH4ExXtwplTX+HfXF+ZHYlicAN39hy4Hg==","2":"J+wJFTPEue6jjdFMOj7N7wqZweVky+Zt/gCCUBVOeDmwt1Io/o3rzEyjMOauvBq8dAcLycnB4muTnJ2RbkXhPA==","3":"J+wJFTPEue6jjdFMOj7N7wqZweVky+Zt/gCCUBVOeDmwt1Io/o3rzEyjMOauvBq8dAcLycnB4muTnJ2RbkXhPA==","4":"AzJEYS/PR0PlAxObuY7LO49YiE7ZzmQEkeYH2q5zuo6jibYQEj8eMOt5OUJ7S9Tm8FA2ISt4A896mQ0AzbnzkQ==","5":"nDoRoI0yqV9IjNB2uo350iYi6n5Lt4pHCXTULT+xK3JKt5vqWIBfXxQ6GOG/UXgO0TqdsXa6LafHXpk7nXKO1A==","6":"J+wJFTPEue6jjdFMOj7N7wqZweVky+Zt/gCCUBVOeDmwt1Io/o3rzEyjMOauvBq8dAcLycnB4muTnJ2RbkXhPA==","7":"EwEiYgkAsHRj8E/HibHPs+70skpm/DW2qndLIIOrkCyJI6hQ/qU7DT2Z3y2K53wQY6cMi7mghOmdd+zEzdQqaA==","8":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","9":"q+0IQZ+v0CXBRtp/713PV0tfmugaqp5d54cFAh1/U00jd6XRARPTH3HIzLdaRoS/8VMbLroajxG2rbjillZx7Q==","10":"PGsjFalFg+t3V9gqkRdoHWEi3ozyFDbmZ5MXde3yAayDCSAXJOKeHAs0nFo1hw4CZTihAqwv8T0UqHpj2bouNg==","11":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","12":"3ltx4yZdd8DxyUHWWoFbnslWbDOMeC+PzycaZDaGfY5BiJCswjtTCepibUoTmNUgotrOvkUjpJVHI0ql0UDkAQ==","13":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","14":"hq9AIsVNou36QDe1Sj/m4/xe4tQKngqZiNkq1tB1hnf7gr2NyjHjuVnzzpNd1StUCqW269zXIBUdYI7EeR31zQ==","15":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","16":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","17":"/////////////////////////////////////////////////////////////////////////////////////w==","18":"/////////////////////////////////////////////////////////////////////////////////////w==","19":"/////////////////////////////////////////////////////////////////////////////////////w==","20":"/////////////////////////////////////////////////////////////////////////////////////w==","21":"/////////////////////////////////////////////////////////////////////////////////////w==","22":"/////////////////////////////////////////////////////////////////////////////////////w==","23":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="}}}],"eventLog":"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","sevSnpAttestation":{"report":{"version":2,"policy":"196608","familyId":"AAAAAAAAAAAAAAAAAAAAAA==","imageId":"AAAAAAAAAAAAAAAAAAAAAA==","signatureAlgo":1,"currentTcb":"14846679121580261379","platformInfo":"3","authorKeyEn":4,"reportData":"s8UkmiGCFBMyYvWpQ1cIkOypnw8s9uygNvcqDyYYlpYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","measurement":"JayBD1xHUIkPsaFY8JeWLgTU1/tkDR0IqZgpz0pwVDpHzG+xkrvpCqcTFCNhpmFV","hostData":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","idKeyDigest":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","authorKeyDigest":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","reportId":"c+5Bu3zXghGxmkiPmIrRvLRgxIKIbjC74IEjzlpWXx0=","reportIdMa":"//////////////////////////////////////////8=","reportedTcb":"8289438064128819203","chipId":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","committedTcb":"8289438064128819203","currentBuild":1,"currentMinor":54,"currentMajor":1,"committedBuild":1,"committedMinor":54,"committedMajor":1,"launchTcb":"8289438064128819203","signature":"+I+FKD+izQwR2/MY/8m1WuHsdyJfPI1bs8+82/bNJeyVZcm70kCedk4bIgtg6MznAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ9O0Imt5m+9P2nYGiS+C4ysSgzxuxF8tIU0gLBI059s39Pu4GuQWz14M+b3wEOYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="},"certificateChain":{}}},"InstanceInfo":"eyJSZXBvcnQiOiJBZ0FBQUFBQUFBQUFBQU1BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBRUFBQUFEQUFBQUFBQUt6Z01BQUFBQUFBQUFCQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFKYXlCRDF4SFVJa1BzYUZZOEplV0xnVFUxL3RrRFIwSXFaZ3B6MHB3VkRwSHpHK3hrcnZwQ3FjVEZDTmhwbUZWQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCejdrRzdmTmVDRWJHYVNJK1lpdEc4dEdERWdvaHVNTHZnZ1NQT1dsWmZIZi8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vL0F3QUFBQUFBQ25NQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQXdBQUFBQUFDbk1CTmdFQUFUWUJBQU1BQUFBQUFBcHpBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQVpGdWMzRXRUY3Zjd3FaZzBmekc4R1dpWThGZTJZTDBnZ2pIYWJEckY5M1RjZm8vMGVkeXVadmEyQzN3WVFlM3pBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUdUSFUzYUh1RGlmQTRkbjd1QWVTWE5KMWRUbmErOENHTzRKbGZuTXJCNGdSUk9KV01hZEJrRWptbFJGY2IxRnBBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBPSIsIkNlcnRzIjoicUFkTHdxSmFTRDZxNWpuQVJhQzRvVEFBQUFBckJRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU1JSUZKekNDQXRhZ0F3SUJBZ0lCQURCR0Jna3Foa2lHOXcwQkFRb3dPYUFQTUEwR0NXQ0dTQUZsQXdRQ0FnVUFvUnd3R2dZSktvWklodmNOQVFFSU1BMEdDV0NHU0FGbEF3UUNBZ1VBb2dNQ0FUQ2pBd0lCQVRDQmdERVVNQklHQTFVRUN3d0xSVzVuYVc1bFpYSnBibWN4Q3pBSkJnTlZCQVlUQWxWVE1SUXdFZ1lEVlFRSERBdFRZVzUwWVNCRGJHRnlZVEVMTUFrR0ExVUVDQXdDUTBFeEh6QWRCZ05WQkFvTUZrRmtkbUZ1WTJWa0lFMXBZM0p2SUVSbGRtbGpaWE14RnpBVkJnTlZCQU1NRGxORlZpMVdURVZMTFUxcGJHRnVNQjRYRFRJek1EUXlNREU0TXpBeE5sb1hEVEkwTURReU1ERTRNekF4Tmxvd2VqRVVNQklHQTFVRUN3d0xSVzVuYVc1bFpYSnBibWN4Q3pBSkJnTlZCQVlUQWxWVE1SUXdFZ1lEVlFRSERBdFRZVzUwWVNCRGJHRnlZVEVMTUFrR0ExVUVDQXdDUTBFeEh6QWRCZ05WQkFvTUZrRmtkbUZ1WTJWa0lFMXBZM0p2SUVSbGRtbGpaWE14RVRBUEJnTlZCQU1NQ0ZORlZpMVdURVZMTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVuQnFmMVNiazhLdjdPM0M4VXhOdTUxd3ZiL3c0L3VSM2tPVGo4ZkU3Q0FDZHNUOEliSlQzU3hQNG5IQ3p1ZG5MS1c4WU5CRkpCSFZLUFlSS1BYM2FzWENBQ1JMWDJFQitGVnM0bTFnT281OFFqT2FMVnRNeUc5L0RRVU1rcUZMdW80SHNNSUhwTUJBR0NTc0dBUVFCbkhnQkFRUURBZ0VBTUJRR0NTc0dBUVFCbkhnQkFnUUhGZ1ZOYVd4aGJqQVJCZ29yQmdFRUFaeDRBUU1CQkFNQ0FRTXdFUVlLS3dZQkJBR2NlQUVEQWdRREFnRUFNQkVHQ2lzR0FRUUJuSGdCQXdRRUF3SUJBREFSQmdvckJnRUVBWng0QVFNRkJBTUNBUUF3RVFZS0t3WUJCQUdjZUFFREJnUURBZ0VBTUJFR0Npc0dBUVFCbkhnQkF3Y0VBd0lCQURBUkJnb3JCZ0VFQVp4NEFRTURCQU1DQVFvd0VRWUtLd1lCQkFHY2VBRURDQVFEQWdGek1DY0dDU3NHQVFRQm5IZ0JCUVFhRmhoRFRqMWpZeTEwWlhOMExtRnRZWHB2Ym1GM2N5NWpiMjB3UmdZSktvWklodmNOQVFFS01EbWdEekFOQmdsZ2hrZ0JaUU1FQWdJRkFLRWNNQm9HQ1NxR1NJYjNEUUVCQ0RBTkJnbGdoa2dCWlFNRUFnSUZBS0lEQWdFd293TUNBUUVEZ2dJQkFDOWR3YkxBUUNPNUR2SU8wdGVSY0ZRSDlzdnN0YnFhTDZRMVR4djdTQWJudi9sMnBmYWlSMHdwcU9CS2lZcHFhWjI2RXJDK2lMdTB0eE5wYXg1VXhzMlViUmZaaVE2OURzYUxORWI1OEpNVW5odFh4RWNPY092YmtSdTFIWXB2Um5qbkVNcVRMNmp0YzJvZ25VUmxiL3E2UkVmdUxrSzVHbmg4WUVtNVRid3JNZjVYOTRMQWFKVEp3ck0wQnVrUE1wMENMRTcxN1VjR0g0bFcwRXdvdjBlbm5iMjM4STQxcEhoWW5icGM3YjFtMHlXZStHcnM3RktPcElpWWNQR0wrUUJIY1VZempVSjBuNzhLYnlSVEpwbUhaczhiMkVTZlEwb0FGUnVqeEtvU2dTQXl0OFc2d3F5UWpObTVBR2RKSlhwalExYktrc0trZnQ5eWlYa1hUcC9nbHUxbEUzOEdtOGlHWlpGVGV5L3crS1pTZVdQZjJEb1k2b0h1RTZNWFN1akYwZ0M5M0l6U2dUUXc2WkJJRjEvOEM1WVVOazlOREJKNFFTOUg2TjdEZ0JsdnhyN2llOGdEZS94a0x1U3hBUEVmYk9KWnRZKzVDTnNxUFBaUGhHQzdnM3FnOU1xdXUzdnRJdXFGMlpoTks1ekt5bk50cEwzaXk3RW5QbENHOWpReUlHSjA2SHJjcUFNS2FtRmtEVGx0THpBYlRpbHpIYUxDcmp1WW1ieFJYbjBTRStjRE82N0kya1p0ZXhWSWwwWXhEZmh5ZTFYKzJqM2tFN1phR3JZell4THN0WkNJbFZVejB3VEtvUmk5MGt6OThjR3hYOC9hUXN0TUhZWUpIaGxKcHJlYlhvcGxxcDdOdzZYTkttbi9HWEF4ZkJId2RQeUYvV01OZXpUc0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9PSJ9","UserData":"BWmTa8zt+6Y1nbl1twWt8tRpMz9TQkmUjUPTq8oOW30="}` - type simTPMWithEventLog struct { io.ReadWriteCloser } @@ -448,45 +444,6 @@ func TestGetSelectedMeasurements(t *testing.T) { } } -// TestUnmarshal verifies that an actual attestation document can be unmarshalled. -func TestUnmarshal(t *testing.T) { - assert := assert.New(t) - - attestation := &AttestationDocument{} - err := json.Unmarshal([]byte(attDocRaw), attestation) - assert.NoError(err) -} - -// TestMarshalUnmarshal verifies that the AttestationDocument can be marshalled and unmarshalled. -func TestMarshalUnmarshal(t *testing.T) { - testCases := map[string]struct { - obj AttestationDocument - }{ - "snp tee": { - obj: AttestationDocument{&attest.Attestation{TeeAttestation: &attest.Attestation_SevSnpAttestation{SevSnpAttestation: &sevsnp.Attestation{Report: &sevsnp.Report{Version: 1}}}}, nil, nil}, - }, - "no tee": { - obj: AttestationDocument{&attest.Attestation{}, nil, nil}, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - data, err := json.Marshal(&tc.obj) - assert.NoError(err) - assert.NotNil(data) - - attestation := &AttestationDocument{} - err = json.Unmarshal(data, attestation) - assert.NoError(err) - assert.NotNil(attestation) - assert.Equal(tc.obj, *attestation) - }) - } -} - type testAttestationLogger struct { infos []string warnings []string diff --git a/internal/config/attestation_test.go b/internal/config/attestation_test.go index 9f0cb89e6..650fef866 100644 --- a/internal/config/attestation_test.go +++ b/internal/config/attestation_test.go @@ -27,7 +27,7 @@ func TestUnmarshalAttestationConfig(t *testing.T) { cfg AttestationCfg }{ "AWSSEVSNP": { - cfg: DefaultForAWSSEVSNP(), + cfg: &AWSSEVSNP{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{}), LaunchMeasurement: measurements.PlaceHolderMeasurement(48)}, }, "AWSNitroTPM": { cfg: &AWSNitroTPM{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSNitroTPM{})}, diff --git a/internal/config/azure.go b/internal/config/azure.go index ff24b993a..e7a39a864 100644 --- a/internal/config/azure.go +++ b/internal/config/azure.go @@ -16,7 +16,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/constants" ) // DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation. @@ -33,7 +32,7 @@ func DefaultForAzureSEVSNP() *AzureSEVSNP { EnforcementPolicy: idkeydigest.MAAFallback, }, // AMD root key. Received from the AMD Key Distribution System API (KDS). - AMDRootKey: mustParsePEM(constants.AMDRootKey), + AMDRootKey: mustParsePEM(`-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n`), } } diff --git a/internal/config/config.go b/internal/config/config.go index bf4485eea..7e8ca54a4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,6 +19,7 @@ All config relevant definitions, parsing and validation functions should go here package config import ( + "bytes" "context" "errors" "fmt" @@ -357,7 +358,7 @@ func Default() *Config { // AWS uses aws-nitro-tpm as attestation variant // AWS will have aws-sev-snp as attestation variant Attestation: AttestationConfig{ - AWSSEVSNP: DefaultForAWSSEVSNP(), + AWSSEVSNP: &AWSSEVSNP{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{}), LaunchMeasurement: measurements.WithAllBytes(0x00, measurements.Enforce, measurements.PCRMeasurementLength)}, AWSNitroTPM: &AWSNitroTPM{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSNitroTPM{})}, AzureSEVSNP: DefaultForAzureSEVSNP(), AzureTrustedLaunch: &AzureTrustedLaunch{Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTrustedLaunch{})}, @@ -787,22 +788,9 @@ type AWSSEVSNP struct { // description: | // Expected TPM measurements. Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"` - // TODO (derpsteb): reenable launchMeasurement once we have a way to generate the expected value dynamically. // description: | - // Expected launch measurement in SNP report. Not in use right now. - // LaunchMeasurement measurements.Measurement `json:"launchMeasurement" yaml:"launchMeasurement" validate:"required"` - // description: | - // AMD Root Key certificate used to verify the SEV-SNP certificate chain. - AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"` -} - -// DefaultForAWSSEVSNP provides a valid default configuration for AWS SEV-SNP attestation. -func DefaultForAWSSEVSNP() *AWSSEVSNP { - return &AWSSEVSNP{ - Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{}), - // LaunchMeasurement: measurements.PlaceHolderMeasurement(48), - AMDRootKey: mustParsePEM(constants.AMDRootKey), - } + // Expected launch measurement in SNP report. + LaunchMeasurement measurements.Measurement `json:"launchMeasurement" yaml:"launchMeasurement" validate:"required"` } // GetVariant returns aws-sev-snp as the variant. @@ -826,13 +814,12 @@ func (c AWSSEVSNP) EqualTo(other AttestationCfg) (bool, error) { if !ok { return false, fmt.Errorf("cannot compare %T with %T", c, other) } - // TODO (derpsteb): reenable launchMeasurement once we have a way to generate the expected value dynamically. - // if !bytes.Equal(c.LaunchMeasurement.Expected, otherCfg.LaunchMeasurement.Expected) { - // return false, nil - // } - // if c.LaunchMeasurement.ValidationOpt != otherCfg.LaunchMeasurement.ValidationOpt { - // return false, nil - // } + if !bytes.Equal(c.LaunchMeasurement.Expected, otherCfg.LaunchMeasurement.Expected) { + return false, nil + } + if c.LaunchMeasurement.ValidationOpt != otherCfg.LaunchMeasurement.ValidationOpt { + return false, nil + } return c.Measurements.EqualTo(otherCfg.Measurements), nil } diff --git a/internal/config/config_doc.go b/internal/config/config_doc.go index 0a0339012..3bf4ee75c 100644 --- a/internal/config/config_doc.go +++ b/internal/config/config_doc.go @@ -478,11 +478,11 @@ func init() { AWSSEVSNPDoc.Fields[0].Note = "" AWSSEVSNPDoc.Fields[0].Description = "Expected TPM measurements." AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." - AWSSEVSNPDoc.Fields[1].Name = "amdRootKey" - AWSSEVSNPDoc.Fields[1].Type = "Certificate" + AWSSEVSNPDoc.Fields[1].Name = "launchMeasurement" + AWSSEVSNPDoc.Fields[1].Type = "Measurement" AWSSEVSNPDoc.Fields[1].Note = "" - AWSSEVSNPDoc.Fields[1].Description = "TODO (derpsteb): reenable launchMeasurement once we have a way to generate the expected value dynamically.\ndescription: |\n Expected launch measurement in SNP report. Not in use right now.\nLaunchMeasurement measurements.Measurement `json:\"launchMeasurement\" yaml:\"launchMeasurement\" validate:\"required\"`\ndescription: |\n AMD Root Key certificate used to verify the SEV-SNP certificate chain.\n" - AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "TODO (derpsteb): reenable launchMeasurement once we have a way to generate the expected value dynamically." + AWSSEVSNPDoc.Fields[1].Description = "Expected launch measurement in SNP report." + AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Expected launch measurement in SNP report." AWSNitroTPMDoc.Type = "AWSNitroTPM" AWSNitroTPMDoc.Comments[encoder.LineComment] = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation." diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e91ad70ca..d77e6d9b3 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -483,11 +483,11 @@ func TestConfig_UpdateMeasurements(t *testing.T) { { // AWS conf := Default() conf.RemoveProviderAndAttestationExcept(cloudprovider.AWS) - for k := range conf.Attestation.AWSNitroTPM.Measurements { - delete(conf.Attestation.AWSNitroTPM.Measurements, k) + for k := range conf.Attestation.AWSSEVSNP.Measurements { + delete(conf.Attestation.AWSSEVSNP.Measurements, k) } conf.UpdateMeasurements(newMeasurements) - assert.Equal(newMeasurements, conf.Attestation.AWSNitroTPM.Measurements) + assert.Equal(newMeasurements, conf.Attestation.AWSSEVSNP.Measurements) } { // Azure conf := Default() diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 76227cead..d9a37dd08 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -218,13 +218,6 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELcPl4Ik+qZuH4K049wksoXK/Os3Z b92PDCpM7FZAINQF88s1TZS/HmRXYk62UJ4eqPduvUnJmXhNikhLbMi6fw== -----END PUBLIC KEY----- ` - - // - // AMD SEV-SNP. - // - - // AMDRootKey is the root certificate for signatures from the AMD SEV-SNP PKI. - AMDRootKey = `-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n` ) // VersionInfo returns the version of a binary.