mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
attestation: use SNP-based attestation for AWS SNP
This commit is contained in:
parent
cdc91b50bc
commit
07eed0e319
@ -4,6 +4,7 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
go_library(
|
||||
name = "snp",
|
||||
srcs = [
|
||||
"errors.go",
|
||||
"issuer.go",
|
||||
"snp.go",
|
||||
"validator.go",
|
||||
@ -12,12 +13,17 @@ go_library(
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation",
|
||||
"//internal/attestation/snp",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"@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_sev_guest//abi",
|
||||
"@com_github_google_go_sev_guest//client",
|
||||
"@com_github_google_go_sev_guest//kds",
|
||||
"@com_github_google_go_sev_guest//proto/sevsnp",
|
||||
"@com_github_google_go_sev_guest//validate",
|
||||
"@com_github_google_go_sev_guest//verify",
|
||||
"@com_github_google_go_sev_guest//verify/trust",
|
||||
"@com_github_google_go_tpm//legacy/tpm2",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
"@com_github_google_go_tpm_tools//proto/attest",
|
||||
@ -37,12 +43,16 @@ go_test(
|
||||
"//conditions:default": ["disable_tpm_simulator"],
|
||||
}),
|
||||
deps = [
|
||||
"//internal/attestation",
|
||||
"//internal/attestation/aws/snp/testdata",
|
||||
"//internal/attestation/simulator",
|
||||
"//internal/attestation/snp",
|
||||
"//internal/attestation/vtpm",
|
||||
"@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",
|
||||
"//internal/config",
|
||||
"//internal/logger",
|
||||
"@com_github_google_go_sev_guest//abi",
|
||||
"@com_github_google_go_sev_guest//proto/sevsnp",
|
||||
"@com_github_google_go_sev_guest//verify",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
"@com_github_google_go_tpm_tools//proto/attest",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
|
48
internal/attestation/aws/snp/errors.go
Normal file
48
internal/attestation/aws/snp/errors.go
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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("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
|
||||
}
|
@ -8,15 +8,20 @@ package snp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"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/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
sevclient "github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-tpm-tools/client"
|
||||
tpmclient "github.com/google/go-tpm-tools/client"
|
||||
)
|
||||
@ -33,7 +38,7 @@ func NewIssuer(log attestation.Logger) *Issuer {
|
||||
Issuer: vtpm.NewIssuer(
|
||||
vtpm.OpenVTPM,
|
||||
getAttestationKey,
|
||||
getInstanceInfo(imds.New(imds.Options{})),
|
||||
getInstanceInfo,
|
||||
log,
|
||||
),
|
||||
}
|
||||
@ -43,24 +48,76 @@ func NewIssuer(log attestation.Logger) *Issuer {
|
||||
func getAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
|
||||
tpmAk, err := client.AttestationKeyRSA(tpm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating RSA Endorsement key: %w", err)
|
||||
return nil, fmt.Errorf("creating RSA Endorsement key: %w", err)
|
||||
}
|
||||
|
||||
return tpmAk, nil
|
||||
}
|
||||
|
||||
// getInstanceInfo returns information about the current instance using the aws Metadata SDK.
|
||||
// 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.
|
||||
// The returned bytes will be written into the attestation document.
|
||||
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)
|
||||
func getInstanceInfo(_ context.Context, tpm io.ReadWriteCloser, _ []byte) ([]byte, error) {
|
||||
tpmAk, err := client.AttestationKeyRSA(tpm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating RSA Endorsement key: %w", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
vlek, err := pemEncodedVLEK(certs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing vlek: %w", err)
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(snp.InstanceInfo{AttestationReport: report, ReportSigner: vlek})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshalling instance info: %w", err)
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
type awsMetaData interface {
|
||||
GetInstanceIdentityDocument(context.Context, *imds.GetInstanceIdentityDocumentInput, ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error)
|
||||
// pemEncodedVLEK takes a marshalled SNP certificate table and returns the PEM-encoded VLEK certificate.
|
||||
// AMD documentation on certificate tables can be found in section 4.1.8.1, revision 2.03 "SEV-ES Guest-Hypervisor Communication Block Standardization".
|
||||
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56421.pdf
|
||||
func pemEncodedVLEK(certs []byte) ([]byte, error) {
|
||||
certTable := abi.CertTable{}
|
||||
if err := certTable.Unmarshal(certs); err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling SNP certificate table: %w", err)
|
||||
}
|
||||
|
||||
vlekRaw, err := certTable.GetByGUIDString(abi.VlekGUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting VLEK certificate: %w", err)
|
||||
}
|
||||
|
||||
// An optional check for certificate well-formedness. vlekRaw == cert.Raw.
|
||||
cert, err := x509.ParseCertificate(vlekRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing certificate: %w", err)
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
})
|
||||
|
||||
return certPEM, nil
|
||||
}
|
||||
|
@ -7,13 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
package snp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"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"
|
||||
@ -25,6 +21,7 @@ func TestGetAttestationKey(t *testing.T) {
|
||||
if cgo == "0" {
|
||||
t.Skip("skipping test because CGO is disabled and tpm simulator requires it")
|
||||
}
|
||||
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
@ -32,7 +29,7 @@ func TestGetAttestationKey(t *testing.T) {
|
||||
require.NoError(err)
|
||||
defer tpm.Close()
|
||||
|
||||
// create the attestation ket in RSA format
|
||||
// create the attestation key in RSA format
|
||||
tpmAk, err := tpmclient.AttestationKeyRSA(tpm)
|
||||
assert.NoError(err)
|
||||
assert.NotNil(tpmAk)
|
||||
@ -45,83 +42,3 @@ 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) {
|
||||
cgo := os.Getenv("CGO_ENABLED")
|
||||
if cgo == "0" {
|
||||
t.Skip("skipping test because CGO is disabled and tpm simulator requires it")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
13
internal/attestation/aws/snp/testdata/BUILD.bazel
vendored
Normal file
13
internal/attestation/aws/snp/testdata/BUILD.bazel
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "testdata",
|
||||
srcs = ["testdata.go"],
|
||||
embedsrcs = [
|
||||
"certchain.pem",
|
||||
"vlek.pem",
|
||||
"report.txt",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/aws/snp/testdata",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
75
internal/attestation/aws/snp/testdata/certchain.pem
vendored
Normal file
75
internal/attestation/aws/snp/testdata/certchain.pem
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
0YPk
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC
|
||||
BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS
|
||||
BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg
|
||||
Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp
|
||||
Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy
|
||||
MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS
|
||||
BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j
|
||||
ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg
|
||||
W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta
|
||||
1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2
|
||||
SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0
|
||||
60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05
|
||||
gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg
|
||||
bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs
|
||||
+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi
|
||||
Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ
|
||||
eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18
|
||||
fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j
|
||||
WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI
|
||||
rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG
|
||||
KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG
|
||||
SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI
|
||||
AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel
|
||||
ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw
|
||||
STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK
|
||||
dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq
|
||||
zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp
|
||||
KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e
|
||||
pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq
|
||||
HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh
|
||||
3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn
|
||||
JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH
|
||||
CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4
|
||||
AFZEAwoKCQ==
|
||||
-----END CERTIFICATE-----
|
1
internal/attestation/aws/snp/testdata/report.txt
vendored
Normal file
1
internal/attestation/aws/snp/testdata/report.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAK0QMAAAAAAAAABAAAAAAAAAADJjVhPI4zH6KeCWNxkQ/mofaTg92gLJRhQApwtm2Ho9pd2GMAJSK+Q6/DTywjOYm9bkAeNR0Q18yADW9d/PAZJayBD1xHUIkPsaFY8JeWLgTU1/tkDR0IqZgpz0pwVDpHzG+xkrvpCqcTFCNhpmFVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOsAob9aWVVnjx8VNbU/bqGewnLGnBSZbJu8smGfzcN///////////////////////////////////////////AwAAAAAACnMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAACqkBNgEAATYBAAMAAAAAAAqpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAm8z1/Oxcd+Bhdxd1okDoZ9gMiYw5Y/fp74hylcA2Eu+XPt5p+7fqqG7d7YLdJtTuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIOZBrwmRpIFfKDCywiFaiILyguTq/6vefDmdzNBKiRKtjdNiHa0hNgeQFGHspRcZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
28
internal/attestation/aws/snp/testdata/testdata.go
vendored
Normal file
28
internal/attestation/aws/snp/testdata/testdata.go
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Package testdata contains testing data for an attestation process.
|
||||
package testdata
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// SNPReport holds a valid VLEK-signed SNP report.
|
||||
//
|
||||
//go:embed report.txt
|
||||
var SNPReport string
|
||||
|
||||
// AKDigest holds the AK digest embedded in SNPReport.REPORT_DATA.
|
||||
const AKDigest = "032635613c8e331fa29e096371910fe6a1f69383dda02c9461400a70b66d87a3da5dd863002522be43afc34f2c233989bd6e401e351d10d7cc800d6f5dfcf019"
|
||||
|
||||
// VLEK for SNPReport.
|
||||
//
|
||||
//go:embed vlek.pem
|
||||
var VLEK []byte
|
||||
|
||||
// CertChain is a valid certificate chain for the VLEK certificate. Queried from AMD KDS.
|
||||
//
|
||||
//go:embed certchain.pem
|
||||
var CertChain []byte
|
30
internal/attestation/aws/snp/testdata/vlek.pem
vendored
Normal file
30
internal/attestation/aws/snp/testdata/vlek.pem
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFLDCCAtugAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAgUA
|
||||
oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBATCBgDEUMBIG
|
||||
A1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtTYW50YSBD
|
||||
bGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFuY2VkIE1pY3JvIERldmlj
|
||||
ZXMxFzAVBgNVBAMMDlNFVi1WTEVLLU1pbGFuMB4XDTIzMDcxOTA4MjkyOFoXDTI0
|
||||
MDcxOTA4MjkyOFowejEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVT
|
||||
MRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFk
|
||||
dmFuY2VkIE1pY3JvIERldmljZXMxETAPBgNVBAMMCFNFVi1WTEVLMHYwEAYHKoZI
|
||||
zj0CAQYFK4EEACIDYgAEXFl4NHpiQCuZXIrehIEk/5XNIdMvo24wyaezN+0FouYB
|
||||
9Z23nL523gpJUlT+mvb5ZMybh5tO1nBGFMOKwzP9dnSBwTs0qn57Ts9OTpW57EAo
|
||||
Mx4SI7g1yz/mt4e6hma4o4HxMIHuMBAGCSsGAQQBnHgBAQQDAgEAMBQGCSsGAQQB
|
||||
nHgBAgQHFgVNaWxhbjARBgorBgEEAZx4AQMBBAMCAQMwEQYKKwYBBAGceAEDAgQD
|
||||
AgEAMBEGCisGAQQBnHgBAwQEAwIBADARBgorBgEEAZx4AQMFBAMCAQAwEQYKKwYB
|
||||
BAGceAEDBgQDAgEAMBEGCisGAQQBnHgBAwcEAwIBADARBgorBgEEAZx4AQMDBAMC
|
||||
AQowEQYKKwYBBAGceAEDCAQDAgFzMCwGCSsGAQQBnHgBBQQfFh1DTj1jYy11cy1l
|
||||
YXN0LTIuYW1hem9uYXdzLmNvbTBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQC
|
||||
AgUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBAQOCAgEA
|
||||
E2CR10QkVTofcjmQbuu787J+H+OjzQLPIi/dUbP/LvZdYi/eWglYQPRbYxhxnIi1
|
||||
PB9R9c7LLhbNRhroog+TzrxyKLibEAW3rwn2iygPnsIemyL89wqtPNqEKNjhBXsb
|
||||
s/0bmf0rNJ3lugssCAzrIStkx8at0K/099BEs4FuUM5u97HVy+jqLdRa2XOHMgGa
|
||||
K7sNdR4swuLhfts9gOOX8ntJ+XkxtUx2mz449fXn8KN70mKa2YShhNd2JWJmv1jW
|
||||
K0I1UxVVwIOHBn/W8fQL5a061oRQQaW5+wPRTys0iEMmLU7+plC8LNWeEq93TfFY
|
||||
eUZ9EzinZ5S7z+c8J1FVWYNHGJauWj4lkjf+XGUZqXwTCPzou6tYJqqwWQEUUxXC
|
||||
M3QKgbkIGWg4WKHIAXGChbM86JLY0W6VueOHyu4S1Z4i81IcDp4cs83WxYWfCpKH
|
||||
Fq3Si2BhzZ0YGgK25JCkomh5Yf7dlsByyuQssf3TCqNmOfSFOTLvxfwTvLD5Omlm
|
||||
O1mPI0YaoZya4WcPxbpWS+2Em23/5inQvT+ZhvMNkljD2NVbhLVGP1v4YR+T2zaC
|
||||
0qJ4YYJ2ERQTnEUlKnlF9bm6PwZSRHupK6ecsGjH+Bz5hBPbT09nEpJf0bWkzVSA
|
||||
AY8POFt3zBJiqONQuOlBpXzqKRKvFYQVEaX2EXQ+W6s=
|
||||
-----END CERTIFICATE-----
|
@ -9,99 +9,210 @@ package snp
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
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/snp"
|
||||
"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/kds"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/validate"
|
||||
"github.com/google/go-sev-guest/verify"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm/legacy/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
|
||||
getDescribeClient func(context.Context, string) (awsMetadataAPI, error)
|
||||
// cfg contains version numbers required for the SNP report validation.
|
||||
cfg *config.AWSSEVSNP
|
||||
// reportValidator validates a SNP report. reportValidator is required for testing.
|
||||
reportValidator snpReportValidator
|
||||
// log is used for logging.
|
||||
log attestation.Logger
|
||||
}
|
||||
|
||||
// NewValidator create a new Validator structure and returns it.
|
||||
func NewValidator(cfg *config.AWSSEVSNP, log attestation.Logger) *Validator {
|
||||
v := &Validator{}
|
||||
v := &Validator{
|
||||
cfg: cfg,
|
||||
reportValidator: &awsValidator{httpsGetter: trust.DefaultHTTPSGetter(), verifier: &reportVerifierImpl{}, validator: &reportValidatorImpl{}},
|
||||
log: log,
|
||||
}
|
||||
|
||||
v.Validator = vtpm.NewValidator(
|
||||
cfg.Measurements,
|
||||
getTrustedKey,
|
||||
v.tpmEnabled,
|
||||
v.getTrustedKey,
|
||||
func(vtpm.AttestationDocument, *attest.MachineState) error { return nil },
|
||||
log,
|
||||
)
|
||||
v.getDescribeClient = getEC2Client
|
||||
return v
|
||||
}
|
||||
|
||||
// getTrustedKeys return the public area of the provides attestation key.
|
||||
// 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
|
||||
// getTrustedKeys return the public area of the provided attestation key (AK).
|
||||
// Ideally, the AK should be bound to the TPM via an endorsement key, but currently AWS does not provide one.
|
||||
// The AK's digest is written to the SNP report's userdata field during report generation.
|
||||
// The AK is trusted if the report can be verified and the AK's digest matches the digest of the AK in attDoc.
|
||||
func (v *Validator) getTrustedKey(_ context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error) {
|
||||
pubArea, err := tpm2.DecodePublic(attDoc.Attestation.AkPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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(attDoc, (*x509.Certificate)(&v.cfg.AMDSigningKey), (*x509.Certificate)(&v.cfg.AMDRootKey), akDigest, v.cfg, v.log); err != nil {
|
||||
return nil, fmt.Errorf("validating SNP report: %w", err)
|
||||
}
|
||||
|
||||
return pubArea.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)
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
return [64]byte{}, fmt.Errorf("marshalling public key: %w", err)
|
||||
}
|
||||
|
||||
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 len(imageOutput.Images) == 0 {
|
||||
return fmt.Errorf("aws image %s not found", imageID)
|
||||
}
|
||||
if len(imageOutput.Images) > 1 {
|
||||
return fmt.Errorf("found multiple image references for image ID %s", imageID)
|
||||
}
|
||||
|
||||
if imageOutput.Images[0].TpmSupport == "v2.0" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("aws image %s does not support TPM v2.0", imageID)
|
||||
return sha512.Sum512(pub), nil
|
||||
}
|
||||
|
||||
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
|
||||
// snpReportValidator validates a given SNP report.
|
||||
type snpReportValidator interface {
|
||||
validate(attestation vtpm.AttestationDocument, ask *x509.Certificate, ark *x509.Certificate, ak [64]byte, config *config.AWSSEVSNP, log attestation.Logger) error
|
||||
}
|
||||
|
||||
type awsMetadataAPI interface {
|
||||
DescribeImages(ctx context.Context, params *ec2.DescribeImagesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error)
|
||||
// awsValidator implements the validation for AWS SNP attestation.
|
||||
// The properties exist for unittesting.
|
||||
type awsValidator struct {
|
||||
verifier reportVerifier
|
||||
validator reportValidator
|
||||
httpsGetter trust.HTTPSGetter
|
||||
}
|
||||
|
||||
type reportVerifier interface {
|
||||
SnpAttestation(att *sevsnp.Attestation, opts *verify.Options) error
|
||||
}
|
||||
type reportValidator interface {
|
||||
SnpAttestation(att *sevsnp.Attestation, opts *validate.Options) error
|
||||
}
|
||||
|
||||
type reportValidatorImpl struct{}
|
||||
|
||||
func (r *reportValidatorImpl) SnpAttestation(att *sevsnp.Attestation, opts *validate.Options) error {
|
||||
return validate.SnpAttestation(att, opts)
|
||||
}
|
||||
|
||||
type reportVerifierImpl struct{}
|
||||
|
||||
func (r *reportVerifierImpl) SnpAttestation(att *sevsnp.Attestation, opts *verify.Options) error {
|
||||
return verify.SnpAttestation(att, opts)
|
||||
}
|
||||
|
||||
// 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 (a *awsValidator) validate(attestation vtpm.AttestationDocument, ask *x509.Certificate, ark *x509.Certificate, akDigest [64]byte, config *config.AWSSEVSNP, log attestation.Logger) error {
|
||||
var info snp.InstanceInfo
|
||||
if err := json.Unmarshal(attestation.InstanceInfo, &info); err != nil {
|
||||
return newValidationError(fmt.Errorf("unmarshalling instance info: %w", err))
|
||||
}
|
||||
|
||||
certchain := snp.NewCertificateChain(ask, ark)
|
||||
|
||||
att, err := info.AttestationWithCerts(a.httpsGetter, certchain, log)
|
||||
if err != nil {
|
||||
return newValidationError(fmt.Errorf("getting attestation with certs: %w", err))
|
||||
}
|
||||
|
||||
verifyOpts, err := getVerifyOpts(att)
|
||||
if err != nil {
|
||||
return newValidationError(fmt.Errorf("getting verify options: %w", err))
|
||||
}
|
||||
|
||||
if err := a.verifier.SnpAttestation(att, verifyOpts); err != nil {
|
||||
return newValidationError(fmt.Errorf("verifying SNP attestation: %w", err))
|
||||
}
|
||||
|
||||
validateOpts := &validate.Options{
|
||||
// Check that the attestation key's digest is included in the report.
|
||||
ReportData: akDigest[:],
|
||||
GuestPolicy: abi.SnpPolicy{
|
||||
Debug: false, // Debug means the VM can be decrypted by the host for debugging purposes and thus is not allowed.
|
||||
SMT: true, // Allow Simultaneous Multi-Threading (SMT). Normally, we would want to disable SMT
|
||||
// but AWS machines are currently facing issues if it's disabled.
|
||||
},
|
||||
VMPL: new(int), // Checks that Virtual Machine Privilege Level (VMPL) is 0.
|
||||
// This checks that the reported LaunchTCB version is equal or greater than the minimum specified in the config.
|
||||
// We don't specify Options.MinimumTCB as it only restricts the allowed TCB for Current_ and Reported_TCB.
|
||||
// Because we allow Options.ProvisionalFirmware, there is not security gained in also checking Current_ and Reported_TCB.
|
||||
// We always have to check Launch_TCB as this value indicated the smallest TCB version a VM has seen during
|
||||
// it's lifetime.
|
||||
MinimumLaunchTCB: kds.TCBParts{
|
||||
BlSpl: config.BootloaderVersion.Value, // Bootloader
|
||||
TeeSpl: config.TEEVersion.Value, // TEE (Secure OS)
|
||||
SnpSpl: config.SNPVersion.Value, // SNP
|
||||
UcodeSpl: config.MicrocodeVersion.Value, // Microcode
|
||||
},
|
||||
// Check that CurrentTCB >= CommittedTCB.
|
||||
PermitProvisionalFirmware: true,
|
||||
}
|
||||
|
||||
// Checks if the attestation report matches the given constraints.
|
||||
// Some constraints are implicitly checked by validate.SnpAttestation:
|
||||
// - the report is not expired
|
||||
if err := a.validator.SnpAttestation(att, validateOpts); err != nil {
|
||||
return newValidationError(fmt.Errorf("validating SNP attestation: %w", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVerifyOpts(att *sevsnp.Attestation) (*verify.Options, error) {
|
||||
ask, err := x509.ParseCertificate(att.CertificateChain.AskCert)
|
||||
if err != nil {
|
||||
return &verify.Options{}, fmt.Errorf("parsing VLEK certificate: %w", err)
|
||||
}
|
||||
ark, err := x509.ParseCertificate(att.CertificateChain.ArkCert)
|
||||
if err != nil {
|
||||
return &verify.Options{}, fmt.Errorf("parsing VLEK certificate: %w", err)
|
||||
}
|
||||
|
||||
verifyOpts := &verify.Options{
|
||||
DisableCertFetching: true,
|
||||
TrustedRoots: map[string][]*trust.AMDRootCerts{
|
||||
"Milan": {
|
||||
{
|
||||
Product: "Milan",
|
||||
ProductCerts: &trust.ProductCerts{
|
||||
// When using a VLEK signer, the intermediate certificate has to be stored in Asvk instead of Ask.
|
||||
Asvk: ask,
|
||||
Ark: ark,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return verifyOpts, nil
|
||||
}
|
||||
|
@ -7,41 +7,66 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
package snp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"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"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/aws/snp/testdata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
spb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/verify"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGeTrustedKey(t *testing.T) {
|
||||
func TestGetTrustedKey(t *testing.T) {
|
||||
validator := func() *Validator { return &Validator{reportValidator: stubawsValidator{}} }
|
||||
testCases := map[string]struct {
|
||||
akPub []byte
|
||||
info []byte
|
||||
wantErr bool
|
||||
akPub []byte
|
||||
info []byte
|
||||
wantErr bool
|
||||
assertCorrectError func(error)
|
||||
}{
|
||||
"nul byte docs": {
|
||||
"null 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 := getTrustedKey(
|
||||
out, err := validator().getTrustedKey(
|
||||
context.Background(),
|
||||
vtpm.AttestationDocument{
|
||||
Attestation: &attest.Attestation{
|
||||
@ -54,6 +79,7 @@ func TestGeTrustedKey(t *testing.T) {
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
tc.assertCorrectError(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
@ -63,48 +89,60 @@ func TestGeTrustedKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTpmEnabled(t *testing.T) {
|
||||
idDocNoTPM := imds.InstanceIdentityDocument{
|
||||
ImageID: "ami-tpm-disabled",
|
||||
}
|
||||
userDataNoTPM, _ := json.Marshal(idDocNoTPM)
|
||||
attDocNoTPM := vtpm.AttestationDocument{
|
||||
InstanceInfo: userDataNoTPM,
|
||||
}
|
||||
// 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) {
|
||||
require := require.New(t)
|
||||
certs, err := loadCerts(testdata.CertChain)
|
||||
require.NoError(err)
|
||||
ark := certs[1]
|
||||
ask := certs[0]
|
||||
|
||||
idDocTPM := imds.InstanceIdentityDocument{
|
||||
ImageID: "ami-tpm-enabled",
|
||||
}
|
||||
userDataTPM, _ := json.Marshal(idDocTPM)
|
||||
attDocTPM := vtpm.AttestationDocument{
|
||||
InstanceInfo: userDataTPM,
|
||||
// reportTransformer unpacks the base64 encoded report, applies the given transformations and re-encodes it.
|
||||
reportTransformer := func(reportHex string, transformations func(*spb.Report)) string {
|
||||
rawReport, err := base64.StdEncoding.DecodeString(reportHex)
|
||||
require.NoError(err)
|
||||
report, err := abi.ReportToProto(rawReport)
|
||||
require.NoError(err)
|
||||
transformations(report)
|
||||
reportBytes, err := abi.ReportToAbiBytes(report)
|
||||
require.NoError(err)
|
||||
return base64.StdEncoding.EncodeToString(reportBytes)
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
attDoc vtpm.AttestationDocument
|
||||
awsAPI awsMetadataAPI
|
||||
wantErr bool
|
||||
ak string
|
||||
report string
|
||||
reportTransformer func(string, func(*spb.Report)) string
|
||||
verifier reportVerifier
|
||||
validator reportValidator
|
||||
wantErr bool
|
||||
}{
|
||||
"ami with tpm": {
|
||||
attDoc: attDocNoTPM,
|
||||
awsAPI: &stubDescribeAPI{describeImagesTPMSupport: "v2.0"},
|
||||
"success": {
|
||||
ak: testdata.AKDigest,
|
||||
report: testdata.SNPReport,
|
||||
verifier: &reportVerifierImpl{},
|
||||
validator: &reportValidatorImpl{},
|
||||
},
|
||||
"ami without tpm": {
|
||||
attDoc: attDocTPM,
|
||||
awsAPI: &stubDescribeAPI{describeImagesTPMSupport: "v1.0"},
|
||||
wantErr: true,
|
||||
"invalid report data": {
|
||||
ak: testdata.AKDigest,
|
||||
report: reportTransformer(testdata.SNPReport, func(r *spb.Report) {
|
||||
r.ReportData = make([]byte, 64)
|
||||
}),
|
||||
verifier: &stubReportVerifier{},
|
||||
validator: &reportValidatorImpl{},
|
||||
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,
|
||||
"invalid report signature": {
|
||||
ak: testdata.AKDigest,
|
||||
report: reportTransformer(testdata.SNPReport, func(r *spb.Report) { r.Signature[0]++ }),
|
||||
verifier: &reportVerifierImpl{},
|
||||
validator: &reportValidatorImpl{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -112,35 +150,156 @@ func TestTpmEnabled(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
v := Validator{
|
||||
getDescribeClient: func(context.Context, string) (awsMetadataAPI, error) {
|
||||
return tc.awsAPI, nil
|
||||
},
|
||||
}
|
||||
hash, err := hex.DecodeString(tc.ak)
|
||||
require.NoError(err)
|
||||
|
||||
err := v.tpmEnabled(tc.attDoc, nil)
|
||||
report, err := base64.StdEncoding.DecodeString(tc.report)
|
||||
require.NoError(err)
|
||||
|
||||
info := snp.InstanceInfo{AttestationReport: report, ReportSigner: testdata.VLEK}
|
||||
infoMarshalled, err := json.Marshal(info)
|
||||
require.NoError(err)
|
||||
|
||||
v := awsValidator{httpsGetter: newStubHTTPSGetter(&urlResponseMatcher{}, nil), verifier: tc.verifier, validator: tc.validator}
|
||||
err = v.validate(vtpm.AttestationDocument{InstanceInfo: infoMarshalled}, ask, ark, [64]byte(hash), config.DefaultForAWSSEVSNP(), logger.NewTest(t))
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubDescribeAPI struct {
|
||||
describeImagesErr error
|
||||
describeImagesTPMSupport string
|
||||
type stubHTTPSGetter struct {
|
||||
urlResponseMatcher *urlResponseMatcher // maps responses to requested URLs
|
||||
err error
|
||||
}
|
||||
|
||||
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)},
|
||||
func newStubHTTPSGetter(urlResponseMatcher *urlResponseMatcher, err error) *stubHTTPSGetter {
|
||||
return &stubHTTPSGetter{
|
||||
urlResponseMatcher: urlResponseMatcher,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubHTTPSGetter) Get(url string) ([]byte, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return s.urlResponseMatcher.match(url)
|
||||
}
|
||||
|
||||
type urlResponseMatcher struct {
|
||||
certChainResponse []byte
|
||||
wantCertChainRequest bool
|
||||
vcekResponse []byte
|
||||
wantVcekRequest bool
|
||||
}
|
||||
|
||||
func (m *urlResponseMatcher) match(url string) ([]byte, error) {
|
||||
switch {
|
||||
case url == "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain":
|
||||
if !m.wantCertChainRequest {
|
||||
return nil, fmt.Errorf("unexpected cert_chain request")
|
||||
}
|
||||
return m.certChainResponse, nil
|
||||
case regexp.MustCompile(`https:\/\/kdsintf.amd.com\/vcek\/v1\/Milan\/.*`).MatchString(url):
|
||||
if !m.wantVcekRequest {
|
||||
return nil, fmt.Errorf("unexpected VCEK request")
|
||||
}
|
||||
return m.vcekResponse, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected URL: %s", url)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
return output, a.describeImagesErr
|
||||
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 loadCerts(pemData []byte) ([]*x509.Certificate, error) {
|
||||
var certs []*x509.Certificate
|
||||
|
||||
for len(pemData) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemData = pem.Decode(pemData)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
if len(certs) == 0 {
|
||||
return nil, errors.New("no valid certificates found")
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
type stubawsValidator struct{}
|
||||
|
||||
func (stubawsValidator) validate(_ vtpm.AttestationDocument, _ *x509.Certificate, _ *x509.Certificate, _ [64]byte, _ *config.AWSSEVSNP, _ attestation.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubReportVerifier struct{}
|
||||
|
||||
func (stubReportVerifier) SnpAttestation(_ *sevsnp.Attestation, _ *verify.Options) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
|
||||
return nil, fmt.Errorf("unmarshalling instanceInfo: %w", err)
|
||||
}
|
||||
|
||||
att, err := instanceInfo.AttestationWithCerts(v.log, v.getter, cachedCerts)
|
||||
att, err := instanceInfo.AttestationWithCerts(v.getter, cachedCerts, v.log)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing attestation report: %w", err)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ go_test(
|
||||
"//internal/config",
|
||||
"//internal/logger",
|
||||
"@com_github_google_go_sev_guest//kds",
|
||||
"@com_github_google_go_sev_guest//verify/trust",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
|
@ -24,8 +24,7 @@ import (
|
||||
|
||||
// InstanceInfo contains the necessary information to establish trust in a SNP CVM.
|
||||
type InstanceInfo struct {
|
||||
// ReportSigner is the PEM-encoded ReportSigner/VLEK certificate for the attestation report.
|
||||
// Public key that validates the report's signature.
|
||||
// ReportSigner is the PEM-encoded certificate used to validate the attestation report's signature.
|
||||
ReportSigner []byte
|
||||
// CertChain is the PEM-encoded certificate chain for the attestation report (ASK+ARK).
|
||||
// Intermediate key that validates the ReportSigner and root key.
|
||||
@ -44,13 +43,56 @@ type AzureInstanceInfo struct {
|
||||
MAAToken string
|
||||
}
|
||||
|
||||
// addReportSigner parses the reportSigner certificate (VCEK/VLEK) from a and adds it to the attestation proto att.
|
||||
// If reportSigner is empty and a VLEK is required, an error is returned.
|
||||
// If reportSigner is empty and a VCEK is required, the VCEK is retrieved from AMD KDS.
|
||||
func (a *InstanceInfo) addReportSigner(att *spb.Attestation, report *spb.Report, productName string, getter trust.HTTPSGetter, logger attestation.Logger) (abi.ReportSigner, error) {
|
||||
// If the VCEK certificate is present, parse it and format it.
|
||||
reportSigner, err := a.ParseReportSigner()
|
||||
if err != nil {
|
||||
logger.Warnf("Error parsing report signer: %v", err)
|
||||
}
|
||||
|
||||
signerInfo, err := abi.ParseSignerInfo(report.GetSignerInfo())
|
||||
if err != nil {
|
||||
return abi.NoneReportSigner, fmt.Errorf("parsing signer info: %w", err)
|
||||
}
|
||||
|
||||
switch signerInfo.SigningKey {
|
||||
case abi.VlekReportSigner:
|
||||
if reportSigner == nil {
|
||||
return abi.NoneReportSigner, fmt.Errorf("VLEK certificate required but not present")
|
||||
}
|
||||
att.CertificateChain.VlekCert = reportSigner.Raw
|
||||
|
||||
case abi.VcekReportSigner:
|
||||
var vcekData []byte
|
||||
|
||||
// If no VCEK is present, fetch it from AMD.
|
||||
if reportSigner == nil {
|
||||
logger.Infof("VCEK certificate not present, falling back to retrieving it from AMD KDS")
|
||||
vcekURL := kds.VCEKCertURL(productName, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb()))
|
||||
vcekData, err = getter.Get(vcekURL)
|
||||
if err != nil {
|
||||
return abi.NoneReportSigner, fmt.Errorf("retrieving VCEK certificate from AMD KDS: %w", err)
|
||||
}
|
||||
} else {
|
||||
vcekData = reportSigner.Raw
|
||||
}
|
||||
|
||||
att.CertificateChain.VcekCert = vcekData
|
||||
}
|
||||
|
||||
return signerInfo.SigningKey, nil
|
||||
}
|
||||
|
||||
// AttestationWithCerts returns a formatted version of the attestation report and its certificates from the instanceInfo.
|
||||
// Certificates are retrieved in the following precedence:
|
||||
// 1. ASK or ARK from issuer. On Azure: THIM. One AWS: not prefilled.
|
||||
// 2. ASK or ARK from fallbackCerts.
|
||||
// 3. ASK or ARK from AMD KDS.
|
||||
func (a *InstanceInfo) AttestationWithCerts(logger attestation.Logger, getter trust.HTTPSGetter,
|
||||
fallbackCerts CertificateChain,
|
||||
func (a *InstanceInfo) AttestationWithCerts(getter trust.HTTPSGetter,
|
||||
fallbackCerts CertificateChain, logger attestation.Logger,
|
||||
) (*spb.Attestation, error) {
|
||||
report, err := abi.ReportToProto(a.AttestationReport)
|
||||
if err != nil {
|
||||
@ -67,22 +109,10 @@ func (a *InstanceInfo) AttestationWithCerts(logger attestation.Logger, getter tr
|
||||
Product: sevProduct,
|
||||
}
|
||||
|
||||
// If the VCEK certificate is present, parse it and format it.
|
||||
vcek, err := a.ParseVCEK()
|
||||
// Add VCEK/VLEK to attestation object.
|
||||
signingInfo, err := a.addReportSigner(att, report, productName, getter, logger)
|
||||
if err != nil {
|
||||
logger.Warnf("Error parsing VCEK: %v", err)
|
||||
}
|
||||
if vcek != nil {
|
||||
att.CertificateChain.VcekCert = vcek.Raw
|
||||
} else {
|
||||
// Otherwise, retrieve it from AMD KDS.
|
||||
logger.Infof("VCEK certificate not present, falling back to retrieving it from AMD KDS")
|
||||
vcekURL := kds.VCEKCertURL(productName, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb()))
|
||||
vcek, err := getter.Get(vcekURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving VCEK certificate from AMD KDS: %w", err)
|
||||
}
|
||||
att.CertificateChain.VcekCert = vcek
|
||||
return nil, fmt.Errorf("adding report signer: %w", err)
|
||||
}
|
||||
|
||||
// If the certificate chain from THIM is present, parse it and format it.
|
||||
@ -115,7 +145,7 @@ func (a *InstanceInfo) AttestationWithCerts(logger attestation.Logger, getter tr
|
||||
(att.CertificateChain.ArkCert != nil),
|
||||
(att.CertificateChain.AskCert != nil),
|
||||
)
|
||||
kdsCertChain, err := trust.GetProductChain(productName, abi.VcekReportSigner, getter)
|
||||
kdsCertChain, err := trust.GetProductChain(productName, signingInfo, getter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving certificate chain from AMD KDS: %w", err)
|
||||
}
|
||||
@ -174,7 +204,7 @@ func (a *InstanceInfo) ParseCertChain() (ask, ark *x509.Certificate, retErr erro
|
||||
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf
|
||||
// Table 6 and 7
|
||||
switch cert.Subject.CommonName {
|
||||
case "SEV-Milan":
|
||||
case "SEV-Milan", "SEV-VLEK-Milan":
|
||||
ask = cert
|
||||
case "ARK-Milan":
|
||||
ark = cert
|
||||
@ -196,9 +226,9 @@ func (a *InstanceInfo) ParseCertChain() (ask, ark *x509.Certificate, retErr erro
|
||||
return
|
||||
}
|
||||
|
||||
// ParseVCEK parses the VCEK certificate from the instanceInfo into an x509-formatted certificate.
|
||||
// If the VCEK certificate is not present, nil is returned.
|
||||
func (a *InstanceInfo) ParseVCEK() (*x509.Certificate, error) {
|
||||
// ParseReportSigner parses the VCEK/VLEK certificate from the instanceInfo into an x509-formatted certificate.
|
||||
// If no certificate is present, nil is returned.
|
||||
func (a *InstanceInfo) ParseReportSigner() (*x509.Certificate, error) {
|
||||
newlinesTrimmed := bytes.TrimSpace(a.ReportSigner)
|
||||
if len(newlinesTrimmed) == 0 {
|
||||
// VCEK is not present.
|
||||
@ -216,10 +246,10 @@ func (a *InstanceInfo) ParseVCEK() (*x509.Certificate, error) {
|
||||
return nil, fmt.Errorf("expected PEM block type 'CERTIFICATE', got '%s'", block.Type)
|
||||
}
|
||||
|
||||
vcek, err := x509.ParseCertificate(block.Bytes)
|
||||
reportSigner, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing VCEK certificate: %w", err)
|
||||
}
|
||||
|
||||
return vcek, nil
|
||||
return reportSigner, nil
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/google/go-sev-guest/kds"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -113,7 +114,7 @@ func TestParseVCEK(t *testing.T) {
|
||||
ReportSigner: tc.VCEK,
|
||||
}
|
||||
|
||||
vcek, err := instanceInfo.ParseVCEK()
|
||||
vcek, err := instanceInfo.ParseReportSigner()
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
@ -124,10 +125,13 @@ func TestParseVCEK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestInstanceInfoAttestation tests the basic unmarshalling of the attestation report and the ASK / ARK precedence.
|
||||
func TestInstanceInfoAttestation(t *testing.T) {
|
||||
// TestAttestationWithCerts tests the basic unmarshalling of the attestation report and the ASK / ARK precedence.
|
||||
func TestAttestationWithCerts(t *testing.T) {
|
||||
defaultReport := testdata.AttestationReport
|
||||
vlekReport, err := hex.DecodeString(testdata.AttestationReportVLEK)
|
||||
require.NoError(t, err)
|
||||
testdataArk, testdataAsk := mustCertChainToPem(t, testdata.CertChain)
|
||||
testdataArvk, testdataAsvk := mustCertChainToPem(t, testdata.VlekCertChain)
|
||||
exampleCert := &x509.Certificate{
|
||||
Raw: []byte{1, 2, 3},
|
||||
}
|
||||
@ -135,7 +139,8 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
report []byte
|
||||
vcek []byte
|
||||
idkeydigest string
|
||||
reportSigner []byte
|
||||
certChain []byte
|
||||
fallbackCerts CertificateChain
|
||||
getter *stubHTTPSGetter
|
||||
@ -144,15 +149,33 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
report: defaultReport,
|
||||
vcek: testdata.AzureThimVCEK,
|
||||
certChain: testdata.CertChain,
|
||||
expectedArk: testdataArk,
|
||||
expectedAsk: testdataAsk,
|
||||
report: defaultReport,
|
||||
idkeydigest: "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1",
|
||||
reportSigner: testdata.AzureThimVCEK,
|
||||
certChain: testdata.CertChain,
|
||||
expectedArk: testdataArk,
|
||||
expectedAsk: testdataAsk,
|
||||
},
|
||||
"vlek success": {
|
||||
report: vlekReport,
|
||||
idkeydigest: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
reportSigner: testdata.Vlek,
|
||||
expectedArk: testdataArvk,
|
||||
expectedAsk: testdataAsvk,
|
||||
getter: newStubHTTPSGetter(
|
||||
&urlResponseMatcher{
|
||||
certChainResponse: testdata.VlekCertChain,
|
||||
vcekResponse: testdata.Vlek,
|
||||
wantCertChainRequest: true,
|
||||
wantVcekRequest: true,
|
||||
},
|
||||
nil,
|
||||
),
|
||||
},
|
||||
"retrieve vcek": {
|
||||
report: defaultReport,
|
||||
certChain: testdata.CertChain,
|
||||
report: defaultReport,
|
||||
idkeydigest: "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1",
|
||||
certChain: testdata.CertChain,
|
||||
getter: newStubHTTPSGetter(
|
||||
&urlResponseMatcher{
|
||||
vcekResponse: testdata.AmdKdsVCEK,
|
||||
@ -164,8 +187,9 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
expectedAsk: testdataAsk,
|
||||
},
|
||||
"retrieve certchain": {
|
||||
report: defaultReport,
|
||||
vcek: testdata.AzureThimVCEK,
|
||||
report: defaultReport,
|
||||
idkeydigest: "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1",
|
||||
reportSigner: testdata.AzureThimVCEK,
|
||||
getter: newStubHTTPSGetter(
|
||||
&urlResponseMatcher{
|
||||
certChainResponse: testdata.CertChain,
|
||||
@ -178,7 +202,8 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
},
|
||||
"use fallback certs": {
|
||||
report: defaultReport,
|
||||
vcek: testdata.AzureThimVCEK,
|
||||
idkeydigest: "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1",
|
||||
reportSigner: testdata.AzureThimVCEK,
|
||||
fallbackCerts: NewCertificateChain(exampleCert, exampleCert),
|
||||
getter: newStubHTTPSGetter(
|
||||
&urlResponseMatcher{},
|
||||
@ -189,8 +214,9 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
},
|
||||
"use certchain with fallback certs": {
|
||||
report: defaultReport,
|
||||
idkeydigest: "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1",
|
||||
certChain: testdata.CertChain,
|
||||
vcek: testdata.AzureThimVCEK,
|
||||
reportSigner: testdata.AzureThimVCEK,
|
||||
fallbackCerts: NewCertificateChain(&x509.Certificate{}, &x509.Certificate{}),
|
||||
getter: newStubHTTPSGetter(
|
||||
&urlResponseMatcher{},
|
||||
@ -200,7 +226,8 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
expectedAsk: testdataAsk,
|
||||
},
|
||||
"retrieve vcek and certchain": {
|
||||
report: defaultReport,
|
||||
report: defaultReport,
|
||||
idkeydigest: "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1",
|
||||
getter: newStubHTTPSGetter(
|
||||
&urlResponseMatcher{
|
||||
certChainResponse: testdata.CertChain,
|
||||
@ -235,10 +262,11 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
instanceInfo := InstanceInfo{
|
||||
AttestationReport: tc.report,
|
||||
CertChain: tc.certChain,
|
||||
ReportSigner: tc.vcek,
|
||||
ReportSigner: tc.reportSigner,
|
||||
}
|
||||
|
||||
att, err := instanceInfo.AttestationWithCerts(logger.NewTest(t), tc.getter, tc.fallbackCerts)
|
||||
defer trust.ClearProductCertCache()
|
||||
att, err := instanceInfo.AttestationWithCerts(tc.getter, tc.fallbackCerts, logger.NewTest(t))
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
@ -247,7 +275,7 @@ func TestInstanceInfoAttestation(t *testing.T) {
|
||||
assert.NotNil(att.CertificateChain)
|
||||
assert.NotNil(att.Report)
|
||||
|
||||
assert.Equal(hex.EncodeToString(att.Report.IdKeyDigest[:]), "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1")
|
||||
assert.Equal(tc.idkeydigest, hex.EncodeToString(att.Report.IdKeyDigest[:]))
|
||||
|
||||
// This is a canary for us: If this fails in the future we possibly downgraded a SVN.
|
||||
// See https://github.com/google/go-sev-guest/blob/14ac50e9ffcc05cd1d12247b710c65093beedb58/validate/validate.go#L336 for decomposition of the values.
|
||||
@ -299,12 +327,12 @@ type urlResponseMatcher struct {
|
||||
|
||||
func (m *urlResponseMatcher) match(url string) ([]byte, error) {
|
||||
switch {
|
||||
case url == "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain":
|
||||
case regexp.MustCompile(`https:\/\/kdsintf.amd.com\/(vcek|vlek)\/v1\/Milan\/cert_chain`).MatchString(url):
|
||||
if !m.wantCertChainRequest {
|
||||
return nil, fmt.Errorf("unexpected cert_chain request")
|
||||
}
|
||||
return m.certChainResponse, nil
|
||||
case regexp.MustCompile(`https:\/\/kdsintf.amd.com\/vcek\/v1\/Milan\/.*`).MatchString(url):
|
||||
case regexp.MustCompile(`https:\/\/kdsintf.amd.com\/(vcek|vlek)\/v1\/Milan\/.*`).MatchString(url):
|
||||
if !m.wantVcekRequest {
|
||||
return nil, fmt.Errorf("unexpected VCEK request")
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ go_library(
|
||||
"certchain.pem",
|
||||
"runtimedata.bin",
|
||||
"vcek.pem",
|
||||
"vlek.pem",
|
||||
"vlekcertchain.pem",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/snp/testdata",
|
||||
visibility = ["//:__subpackages__"],
|
||||
|
15
internal/attestation/snp/testdata/testdata.go
vendored
15
internal/attestation/snp/testdata/testdata.go
vendored
@ -9,11 +9,14 @@ package testdata
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// AttestationBytes is an example attestation report from a Constellation VM.
|
||||
// AttestationReport is an example attestation report from a Constellation VM.
|
||||
//
|
||||
//go:embed attestation.bin
|
||||
var AttestationReport []byte
|
||||
|
||||
// AttestationReportVLEK is an example attestation report signed by a VLEK.
|
||||
const AttestationReportVLEK = "02000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000300000000000ace0300000000000000040000000000000044a93ab043ad14ece9bfa97305d95302c9cc6ed95e17efaf7348ed7a7603e1ca89d12758e089d2abcf5a4dd16a99e3cb4cba8f0b8e8cb8eac3e926f1d2b5cfecc2c84b9364fc9f0f54b04534768c860c6e0e386ad98b96e8b98eca46ac8971d05c531ba48373f054c880cfd1f4a0a84e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008c5d6770df734a203cd061a3698e702caed25e7f744dc060eb9dcba0f2e4bdb2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300000000000a73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000a7301360100013601000300000000000a73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b9853dac65f127574c6a578c11885e1887d4c7ae446237d4273715dd8c05cfe4bd49facc1392f2ca7354c8f0d34d65500000000000000000000000000000000000000000000000004013481e9c6a6bb112818aeba3bd178d788dedf62600b8c7892a8d3df4d880265010e7d833201156364a001e62f47b570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// AzureThimVCEK is an example VCEK certificate (PEM, as returned from Azure THIM) for the AttestationReport.
|
||||
//
|
||||
//go:embed vcek.pem
|
||||
@ -33,3 +36,13 @@ var RuntimeData []byte
|
||||
//
|
||||
//go:embed certchain.pem
|
||||
var CertChain []byte
|
||||
|
||||
// VlekCertChain is a valid certificate chain (PEM) for the VLEK certificate.
|
||||
//
|
||||
//go:embed vlekcertchain.pem
|
||||
var VlekCertChain []byte
|
||||
|
||||
// Vlek is a valid VLEK certificate (PEM).
|
||||
//
|
||||
//go:embed vlek.pem
|
||||
var Vlek []byte
|
||||
|
30
internal/attestation/snp/testdata/vlek.pem
vendored
Normal file
30
internal/attestation/snp/testdata/vlek.pem
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFLDCCAtugAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAgUA
|
||||
oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBATCBgDEUMBIG
|
||||
A1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtTYW50YSBD
|
||||
bGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFuY2VkIE1pY3JvIERldmlj
|
||||
ZXMxFzAVBgNVBAMMDlNFVi1WTEVLLU1pbGFuMB4XDTIzMDcxOTA4MjkyOFoXDTI0
|
||||
MDcxOTA4MjkyOFowejEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVT
|
||||
MRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFk
|
||||
dmFuY2VkIE1pY3JvIERldmljZXMxETAPBgNVBAMMCFNFVi1WTEVLMHYwEAYHKoZI
|
||||
zj0CAQYFK4EEACIDYgAEXFl4NHpiQCuZXIrehIEk/5XNIdMvo24wyaezN+0FouYB
|
||||
9Z23nL523gpJUlT+mvb5ZMybh5tO1nBGFMOKwzP9dnSBwTs0qn57Ts9OTpW57EAo
|
||||
Mx4SI7g1yz/mt4e6hma4o4HxMIHuMBAGCSsGAQQBnHgBAQQDAgEAMBQGCSsGAQQB
|
||||
nHgBAgQHFgVNaWxhbjARBgorBgEEAZx4AQMBBAMCAQMwEQYKKwYBBAGceAEDAgQD
|
||||
AgEAMBEGCisGAQQBnHgBAwQEAwIBADARBgorBgEEAZx4AQMFBAMCAQAwEQYKKwYB
|
||||
BAGceAEDBgQDAgEAMBEGCisGAQQBnHgBAwcEAwIBADARBgorBgEEAZx4AQMDBAMC
|
||||
AQowEQYKKwYBBAGceAEDCAQDAgFzMCwGCSsGAQQBnHgBBQQfFh1DTj1jYy11cy1l
|
||||
YXN0LTIuYW1hem9uYXdzLmNvbTBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQC
|
||||
AgUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBAQOCAgEA
|
||||
E2CR10QkVTofcjmQbuu787J+H+OjzQLPIi/dUbP/LvZdYi/eWglYQPRbYxhxnIi1
|
||||
PB9R9c7LLhbNRhroog+TzrxyKLibEAW3rwn2iygPnsIemyL89wqtPNqEKNjhBXsb
|
||||
s/0bmf0rNJ3lugssCAzrIStkx8at0K/099BEs4FuUM5u97HVy+jqLdRa2XOHMgGa
|
||||
K7sNdR4swuLhfts9gOOX8ntJ+XkxtUx2mz449fXn8KN70mKa2YShhNd2JWJmv1jW
|
||||
K0I1UxVVwIOHBn/W8fQL5a061oRQQaW5+wPRTys0iEMmLU7+plC8LNWeEq93TfFY
|
||||
eUZ9EzinZ5S7z+c8J1FVWYNHGJauWj4lkjf+XGUZqXwTCPzou6tYJqqwWQEUUxXC
|
||||
M3QKgbkIGWg4WKHIAXGChbM86JLY0W6VueOHyu4S1Z4i81IcDp4cs83WxYWfCpKH
|
||||
Fq3Si2BhzZ0YGgK25JCkomh5Yf7dlsByyuQssf3TCqNmOfSFOTLvxfwTvLD5Omlm
|
||||
O1mPI0YaoZya4WcPxbpWS+2Em23/5inQvT+ZhvMNkljD2NVbhLVGP1v4YR+T2zaC
|
||||
0qJ4YYJ2ERQTnEUlKnlF9bm6PwZSRHupK6ecsGjH+Bz5hBPbT09nEpJf0bWkzVSA
|
||||
AY8POFt3zBJiqONQuOlBpXzqKRKvFYQVEaX2EXQ+W6s=
|
||||
-----END CERTIFICATE-----
|
75
internal/attestation/snp/testdata/vlekcertchain.pem
vendored
Normal file
75
internal/attestation/snp/testdata/vlekcertchain.pem
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
0YPk
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC
|
||||
BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS
|
||||
BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg
|
||||
Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp
|
||||
Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy
|
||||
MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS
|
||||
BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j
|
||||
ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg
|
||||
W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta
|
||||
1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2
|
||||
SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0
|
||||
60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05
|
||||
gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg
|
||||
bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs
|
||||
+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi
|
||||
Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ
|
||||
eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18
|
||||
fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j
|
||||
WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI
|
||||
rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG
|
||||
KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG
|
||||
SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI
|
||||
AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel
|
||||
ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw
|
||||
STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK
|
||||
dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq
|
||||
zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp
|
||||
KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e
|
||||
pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq
|
||||
HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh
|
||||
3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn
|
||||
JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH
|
||||
CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4
|
||||
AFZEAwoKCQ==
|
||||
-----END CERTIFICATE-----
|
@ -6,6 +6,7 @@ go_library(
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"attestationversion.go",
|
||||
"aws.go",
|
||||
"azure.go",
|
||||
"config.go",
|
||||
"config_doc.go",
|
||||
|
@ -16,6 +16,9 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
)
|
||||
|
||||
// arkPEM is the PEM encoded AMD root key. Received from the AMD Key Distribution System API (KDS).
|
||||
const arkPEM = `-----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`
|
||||
|
||||
// AttestationCfg is the common interface for passing attestation configs.
|
||||
type AttestationCfg interface {
|
||||
// GetMeasurements returns the measurements that should be used for attestation.
|
||||
|
75
internal/config/aws.go
Normal file
75
internal/config/aws.go
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
)
|
||||
|
||||
// DefaultForAWSSEVSNP provides a valid default configuration for AWS SEV-SNP attestation.
|
||||
func DefaultForAWSSEVSNP() *AWSSEVSNP {
|
||||
return &AWSSEVSNP{
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{}),
|
||||
BootloaderVersion: NewLatestPlaceholderVersion(),
|
||||
TEEVersion: NewLatestPlaceholderVersion(),
|
||||
SNPVersion: NewLatestPlaceholderVersion(),
|
||||
MicrocodeVersion: NewLatestPlaceholderVersion(),
|
||||
AMDRootKey: mustParsePEM(arkPEM),
|
||||
}
|
||||
}
|
||||
|
||||
// GetVariant returns aws-sev-snp as the variant.
|
||||
func (AWSSEVSNP) GetVariant() variant.Variant {
|
||||
return variant.AWSSEVSNP{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AWSSEVSNP) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// SetMeasurements updates a config's measurements using the given measurements.
|
||||
func (c *AWSSEVSNP) SetMeasurements(m measurements.M) {
|
||||
c.Measurements = m
|
||||
}
|
||||
|
||||
// EqualTo returns true if the config is equal to the given config.
|
||||
func (c AWSSEVSNP) EqualTo(other AttestationCfg) (bool, error) {
|
||||
otherCfg, ok := other.(*AWSSEVSNP)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot compare %T with %T", c, other)
|
||||
}
|
||||
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
||||
|
||||
// GetVariant returns aws-nitro-tpm as the variant.
|
||||
func (AWSNitroTPM) GetVariant() variant.Variant {
|
||||
return variant.AWSNitroTPM{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AWSNitroTPM) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// SetMeasurements updates a config's measurements using the given measurements.
|
||||
func (c *AWSNitroTPM) SetMeasurements(m measurements.M) {
|
||||
c.Measurements = m
|
||||
}
|
||||
|
||||
// EqualTo returns true if the config is equal to the given config.
|
||||
func (c AWSNitroTPM) EqualTo(other AttestationCfg) (bool, error) {
|
||||
otherCfg, ok := other.(*AWSNitroTPM)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot compare %T with %T", c, other)
|
||||
}
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
@ -30,8 +30,7 @@ func DefaultForAzureSEVSNP() *AzureSEVSNP {
|
||||
AcceptedKeyDigests: idkeydigest.DefaultList(),
|
||||
EnforcementPolicy: idkeydigest.MAAFallback,
|
||||
},
|
||||
// AMD root key. Received from the AMD Key Distribution System API (KDS).
|
||||
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`),
|
||||
AMDRootKey: mustParsePEM(arkPEM),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,7 +382,7 @@ func Default() *Config {
|
||||
// AWS uses aws-nitro-tpm as attestation variant
|
||||
// AWS will have aws-sev-snp as attestation variant
|
||||
Attestation: AttestationConfig{
|
||||
AWSSEVSNP: &AWSSEVSNP{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{})},
|
||||
AWSSEVSNP: DefaultForAWSSEVSNP(),
|
||||
AWSNitroTPM: &AWSNitroTPM{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSNitroTPM{})},
|
||||
AzureSEVSNP: DefaultForAzureSEVSNP(),
|
||||
AzureTrustedLaunch: &AzureTrustedLaunch{Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTrustedLaunch{})},
|
||||
@ -915,80 +915,6 @@ func (c *Config) setCSPNodeGroupDefaults(csp cloudprovider.Provider) {
|
||||
}
|
||||
}
|
||||
|
||||
// AWSSEVSNP is the configuration for AWS SEV-SNP attestation.
|
||||
type AWSSEVSNP struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
// TODO (derpsteb): reenable launchMeasurement once SNP is fixed on AWS.
|
||||
// description: |
|
||||
// Expected launch measurement in SNP report.
|
||||
// LaunchMeasurement measurements.Measurement `json:"launchMeasurement" yaml:"launchMeasurement" validate:"required"`
|
||||
}
|
||||
|
||||
// GetVariant returns aws-sev-snp as the variant.
|
||||
func (AWSSEVSNP) GetVariant() variant.Variant {
|
||||
return variant.AWSSEVSNP{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AWSSEVSNP) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// SetMeasurements updates a config's measurements using the given measurements.
|
||||
func (c *AWSSEVSNP) SetMeasurements(m measurements.M) {
|
||||
c.Measurements = m
|
||||
}
|
||||
|
||||
// EqualTo returns true if the config is equal to the given config.
|
||||
func (c AWSSEVSNP) EqualTo(other AttestationCfg) (bool, error) {
|
||||
otherCfg, ok := other.(*AWSSEVSNP)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot compare %T with %T", c, other)
|
||||
}
|
||||
// TODO(derpsteb): reenable launchMeasurement once SNP is fixed on AWS.
|
||||
// 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
|
||||
}
|
||||
|
||||
// AWSNitroTPM is the configuration for AWS Nitro TPM attestation.
|
||||
type AWSNitroTPM struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
}
|
||||
|
||||
// GetVariant returns aws-nitro-tpm as the variant.
|
||||
func (AWSNitroTPM) GetVariant() variant.Variant {
|
||||
return variant.AWSNitroTPM{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AWSNitroTPM) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// SetMeasurements updates a config's measurements using the given measurements.
|
||||
func (c *AWSNitroTPM) SetMeasurements(m measurements.M) {
|
||||
c.Measurements = m
|
||||
}
|
||||
|
||||
// EqualTo returns true if the config is equal to the given config.
|
||||
func (c AWSNitroTPM) EqualTo(other AttestationCfg) (bool, error) {
|
||||
otherCfg, ok := other.(*AWSNitroTPM)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot compare %T with %T", c, other)
|
||||
}
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
||||
|
||||
// SNPFirmwareSignerConfig is the configuration for validating the firmware signer.
|
||||
type SNPFirmwareSignerConfig struct {
|
||||
// description: |
|
||||
@ -1104,6 +1030,38 @@ func (c QEMUTDX) EqualTo(other AttestationCfg) (bool, error) {
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
||||
|
||||
// AWSSEVSNP is the configuration for AWS SEV-SNP attestation.
|
||||
type AWSSEVSNP struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
// description: |
|
||||
// Lowest acceptable bootloader version.
|
||||
BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
|
||||
// description: |
|
||||
// Lowest acceptable TEE version.
|
||||
TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
|
||||
// description: |
|
||||
// Lowest acceptable SEV-SNP version.
|
||||
SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
|
||||
// description: |
|
||||
// Lowest acceptable microcode version.
|
||||
MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
|
||||
// description: |
|
||||
// AMD Root Key certificate used to verify the SEV-SNP certificate chain.
|
||||
AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"`
|
||||
// description: |
|
||||
// AMD Signing Key certificate used to verify the SEV-SNP VCEK / VLEK certificate.
|
||||
AMDSigningKey Certificate `json:"amdSigningKey,omitempty" yaml:"amdSigningKey,omitempty" validate:"len=0"`
|
||||
}
|
||||
|
||||
// AWSNitroTPM is the configuration for AWS Nitro TPM attestation.
|
||||
type AWSNitroTPM struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
}
|
||||
|
||||
// AzureSEVSNP is the configuration for Azure SEV-SNP attestation.
|
||||
type AzureSEVSNP struct {
|
||||
// description: |
|
||||
|
@ -21,12 +21,12 @@ var (
|
||||
AttestationConfigDoc encoder.Doc
|
||||
NodeGroupDoc encoder.Doc
|
||||
UnsupportedAppRegistrationErrorDoc encoder.Doc
|
||||
AWSSEVSNPDoc encoder.Doc
|
||||
AWSNitroTPMDoc encoder.Doc
|
||||
SNPFirmwareSignerConfigDoc encoder.Doc
|
||||
GCPSEVESDoc encoder.Doc
|
||||
QEMUVTPMDoc encoder.Doc
|
||||
QEMUTDXDoc encoder.Doc
|
||||
AWSSEVSNPDoc encoder.Doc
|
||||
AWSNitroTPMDoc encoder.Doc
|
||||
AzureSEVSNPDoc encoder.Doc
|
||||
AzureTrustedLaunchDoc encoder.Doc
|
||||
)
|
||||
@ -480,38 +480,6 @@ func init() {
|
||||
UnsupportedAppRegistrationErrorDoc.Description = "UnsupportedAppRegistrationError is returned when the config contains configuration related to now unsupported app registrations."
|
||||
UnsupportedAppRegistrationErrorDoc.Fields = make([]encoder.Doc, 0)
|
||||
|
||||
AWSSEVSNPDoc.Type = "AWSSEVSNP"
|
||||
AWSSEVSNPDoc.Comments[encoder.LineComment] = "AWSSEVSNP is the configuration for AWS SEV-SNP attestation."
|
||||
AWSSEVSNPDoc.Description = "AWSSEVSNP is the configuration for AWS SEV-SNP attestation."
|
||||
AWSSEVSNPDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "awsSEVSNP",
|
||||
},
|
||||
}
|
||||
AWSSEVSNPDoc.Fields = make([]encoder.Doc, 1)
|
||||
AWSSEVSNPDoc.Fields[0].Name = "measurements"
|
||||
AWSSEVSNPDoc.Fields[0].Type = "M"
|
||||
AWSSEVSNPDoc.Fields[0].Note = ""
|
||||
AWSSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
AWSNitroTPMDoc.Type = "AWSNitroTPM"
|
||||
AWSNitroTPMDoc.Comments[encoder.LineComment] = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
|
||||
AWSNitroTPMDoc.Description = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
|
||||
AWSNitroTPMDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "awsNitroTPM",
|
||||
},
|
||||
}
|
||||
AWSNitroTPMDoc.Fields = make([]encoder.Doc, 1)
|
||||
AWSNitroTPMDoc.Fields[0].Name = "measurements"
|
||||
AWSNitroTPMDoc.Fields[0].Type = "M"
|
||||
AWSNitroTPMDoc.Fields[0].Note = ""
|
||||
AWSNitroTPMDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AWSNitroTPMDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
SNPFirmwareSignerConfigDoc.Type = "SNPFirmwareSignerConfig"
|
||||
SNPFirmwareSignerConfigDoc.Comments[encoder.LineComment] = "SNPFirmwareSignerConfig is the configuration for validating the firmware signer."
|
||||
SNPFirmwareSignerConfigDoc.Description = "SNPFirmwareSignerConfig is the configuration for validating the firmware signer."
|
||||
@ -586,6 +554,68 @@ func init() {
|
||||
QEMUTDXDoc.Fields[0].Description = "Expected TDX measurements."
|
||||
QEMUTDXDoc.Fields[0].Comments[encoder.LineComment] = "Expected TDX measurements."
|
||||
|
||||
AWSSEVSNPDoc.Type = "AWSSEVSNP"
|
||||
AWSSEVSNPDoc.Comments[encoder.LineComment] = "AWSSEVSNP is the configuration for AWS SEV-SNP attestation."
|
||||
AWSSEVSNPDoc.Description = "AWSSEVSNP is the configuration for AWS SEV-SNP attestation."
|
||||
AWSSEVSNPDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "awsSEVSNP",
|
||||
},
|
||||
}
|
||||
AWSSEVSNPDoc.Fields = make([]encoder.Doc, 7)
|
||||
AWSSEVSNPDoc.Fields[0].Name = "measurements"
|
||||
AWSSEVSNPDoc.Fields[0].Type = "M"
|
||||
AWSSEVSNPDoc.Fields[0].Note = ""
|
||||
AWSSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
AWSSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
|
||||
AWSSEVSNPDoc.Fields[1].Type = "AttestationVersion"
|
||||
AWSSEVSNPDoc.Fields[1].Note = ""
|
||||
AWSSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
|
||||
AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
|
||||
AWSSEVSNPDoc.Fields[2].Name = "teeVersion"
|
||||
AWSSEVSNPDoc.Fields[2].Type = "AttestationVersion"
|
||||
AWSSEVSNPDoc.Fields[2].Note = ""
|
||||
AWSSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
|
||||
AWSSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
|
||||
AWSSEVSNPDoc.Fields[3].Name = "snpVersion"
|
||||
AWSSEVSNPDoc.Fields[3].Type = "AttestationVersion"
|
||||
AWSSEVSNPDoc.Fields[3].Note = ""
|
||||
AWSSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
|
||||
AWSSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
|
||||
AWSSEVSNPDoc.Fields[4].Name = "microcodeVersion"
|
||||
AWSSEVSNPDoc.Fields[4].Type = "AttestationVersion"
|
||||
AWSSEVSNPDoc.Fields[4].Note = ""
|
||||
AWSSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
|
||||
AWSSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version."
|
||||
AWSSEVSNPDoc.Fields[5].Name = "amdRootKey"
|
||||
AWSSEVSNPDoc.Fields[5].Type = "Certificate"
|
||||
AWSSEVSNPDoc.Fields[5].Note = ""
|
||||
AWSSEVSNPDoc.Fields[5].Description = "AMD Root Key certificate used to verify the SEV-SNP certificate chain."
|
||||
AWSSEVSNPDoc.Fields[5].Comments[encoder.LineComment] = "AMD Root Key certificate used to verify the SEV-SNP certificate chain."
|
||||
AWSSEVSNPDoc.Fields[6].Name = "amdSigningKey"
|
||||
AWSSEVSNPDoc.Fields[6].Type = "Certificate"
|
||||
AWSSEVSNPDoc.Fields[6].Note = ""
|
||||
AWSSEVSNPDoc.Fields[6].Description = "AMD Signing Key certificate used to verify the SEV-SNP VCEK / VLEK certificate."
|
||||
AWSSEVSNPDoc.Fields[6].Comments[encoder.LineComment] = "AMD Signing Key certificate used to verify the SEV-SNP VCEK / VLEK certificate."
|
||||
|
||||
AWSNitroTPMDoc.Type = "AWSNitroTPM"
|
||||
AWSNitroTPMDoc.Comments[encoder.LineComment] = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
|
||||
AWSNitroTPMDoc.Description = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
|
||||
AWSNitroTPMDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "awsNitroTPM",
|
||||
},
|
||||
}
|
||||
AWSNitroTPMDoc.Fields = make([]encoder.Doc, 1)
|
||||
AWSNitroTPMDoc.Fields[0].Name = "measurements"
|
||||
AWSNitroTPMDoc.Fields[0].Type = "M"
|
||||
AWSNitroTPMDoc.Fields[0].Note = ""
|
||||
AWSNitroTPMDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AWSNitroTPMDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
AzureSEVSNPDoc.Type = "AzureSEVSNP"
|
||||
AzureSEVSNPDoc.Comments[encoder.LineComment] = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation."
|
||||
AzureSEVSNPDoc.Description = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation."
|
||||
@ -694,14 +724,6 @@ func (_ UnsupportedAppRegistrationError) Doc() *encoder.Doc {
|
||||
return &UnsupportedAppRegistrationErrorDoc
|
||||
}
|
||||
|
||||
func (_ AWSSEVSNP) Doc() *encoder.Doc {
|
||||
return &AWSSEVSNPDoc
|
||||
}
|
||||
|
||||
func (_ AWSNitroTPM) Doc() *encoder.Doc {
|
||||
return &AWSNitroTPMDoc
|
||||
}
|
||||
|
||||
func (_ SNPFirmwareSignerConfig) Doc() *encoder.Doc {
|
||||
return &SNPFirmwareSignerConfigDoc
|
||||
}
|
||||
@ -718,6 +740,14 @@ func (_ QEMUTDX) Doc() *encoder.Doc {
|
||||
return &QEMUTDXDoc
|
||||
}
|
||||
|
||||
func (_ AWSSEVSNP) Doc() *encoder.Doc {
|
||||
return &AWSSEVSNPDoc
|
||||
}
|
||||
|
||||
func (_ AWSNitroTPM) Doc() *encoder.Doc {
|
||||
return &AWSNitroTPMDoc
|
||||
}
|
||||
|
||||
func (_ AzureSEVSNP) Doc() *encoder.Doc {
|
||||
return &AzureSEVSNPDoc
|
||||
}
|
||||
@ -742,12 +772,12 @@ func GetConfigurationDoc() *encoder.FileDoc {
|
||||
&AttestationConfigDoc,
|
||||
&NodeGroupDoc,
|
||||
&UnsupportedAppRegistrationErrorDoc,
|
||||
&AWSSEVSNPDoc,
|
||||
&AWSNitroTPMDoc,
|
||||
&SNPFirmwareSignerConfigDoc,
|
||||
&GCPSEVESDoc,
|
||||
&QEMUVTPMDoc,
|
||||
&QEMUTDXDoc,
|
||||
&AWSSEVSNPDoc,
|
||||
&AWSNitroTPMDoc,
|
||||
&AzureSEVSNPDoc,
|
||||
&AzureTrustedLaunchDoc,
|
||||
},
|
||||
|
@ -46,22 +46,26 @@ func NewClient(log *logger.Logger, kubeClient kubeClient, attVariant variant.Var
|
||||
// and returns the cached certificates, if applicable.
|
||||
// If the certificate chain cache already exists, nothing is done.
|
||||
func (c *Client) CreateCertChainCache(ctx context.Context) (*CachedCerts, error) {
|
||||
var reportSigner abi.ReportSigner
|
||||
switch c.attVariant {
|
||||
case variant.AzureSEVSNP{}:
|
||||
c.log.Debugf("Creating Azure SEV-SNP certificate chain cache")
|
||||
ask, ark, err := c.createCertChainCache(ctx, abi.VcekReportSigner)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating Azure SEV-SNP certificate chain cache: %w", err)
|
||||
}
|
||||
return &CachedCerts{
|
||||
ask: ask,
|
||||
ark: ark,
|
||||
}, nil
|
||||
// TODO(derpsteb): Add AWS
|
||||
reportSigner = abi.VcekReportSigner
|
||||
case variant.AWSSEVSNP{}:
|
||||
reportSigner = abi.VlekReportSigner
|
||||
default:
|
||||
c.log.Debugf("No certificate chain caching possible for attestation variant %s", c.attVariant)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c.log.Debugf("Creating %s certificate chain cache", c.attVariant)
|
||||
ask, ark, err := c.createCertChainCache(ctx, reportSigner)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating %s certificate chain cache: %w", c.attVariant, err)
|
||||
}
|
||||
return &CachedCerts{
|
||||
ask: ask,
|
||||
ark: ark,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CachedCerts contains the cached certificates.
|
||||
|
@ -105,8 +105,14 @@ func (u *Updatable) configWithCerts(cfg config.AttestationCfg) (config.Attestati
|
||||
}
|
||||
c.AMDSigningKey = config.Certificate(ask)
|
||||
return c, nil
|
||||
case *config.AWSSEVSNP:
|
||||
ask, err := u.getCachedAskCert()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting cached ASK certificate: %w", err)
|
||||
}
|
||||
c.AMDSigningKey = config.Certificate(ask)
|
||||
return c, nil
|
||||
}
|
||||
// TODO(derpsteb): Add AWS SEV-SNP
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user