attestation: use SNP-based attestation for AWS SNP

This commit is contained in:
Otto Bittner 2023-11-06 14:22:44 +01:00
parent cdc91b50bc
commit 07eed0e319
27 changed files with 1106 additions and 402 deletions

View File

@ -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",

View 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
}

View File

@ -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
}

View File

@ -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
}

View 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__"],
)

View 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-----

View File

@ -0,0 +1 @@
AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAK0QMAAAAAAAAABAAAAAAAAAADJjVhPI4zH6KeCWNxkQ/mofaTg92gLJRhQApwtm2Ho9pd2GMAJSK+Q6/DTywjOYm9bkAeNR0Q18yADW9d/PAZJayBD1xHUIkPsaFY8JeWLgTU1/tkDR0IqZgpz0pwVDpHzG+xkrvpCqcTFCNhpmFVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOsAob9aWVVnjx8VNbU/bqGewnLGnBSZbJu8smGfzcN///////////////////////////////////////////AwAAAAAACnMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAACqkBNgEAATYBAAMAAAAAAAqpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAm8z1/Oxcd+Bhdxd1okDoZ9gMiYw5Y/fp74hylcA2Eu+XPt5p+7fqqG7d7YLdJtTuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIOZBrwmRpIFfKDCywiFaiILyguTq/6vefDmdzNBKiRKtjdNiHa0hNgeQFGHspRcZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=

View 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

View 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-----

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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",
],

View File

@ -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
}

View File

@ -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")
}

View File

@ -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__"],

View File

@ -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

View 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-----

View 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-----

View File

@ -6,6 +6,7 @@ go_library(
srcs = [
"attestation.go",
"attestationversion.go",
"aws.go",
"azure.go",
"config.go",
"config_doc.go",

View File

@ -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
View 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
}

View File

@ -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),
}
}

View File

@ -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: |

View File

@ -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,
},

View File

@ -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.

View File

@ -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
}