Skip to content

Commit 5768288

Browse files
snp: ensure we never use ARK supplied by Issuer (#3025)
* Make sure SNP ARK is always loaded from config, or fetched from AMD KDS * GCP: Set validator `reportData` correctly --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems> Co-authored-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
1 parent 049a2af commit 5768288

File tree

9 files changed

+100
-245
lines changed

9 files changed

+100
-245
lines changed

internal/attestation/aws/snp/validator.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ func (a *awsValidator) validate(attestation vtpm.AttestationDocument, ask *x509.
191191
func getVerifyOpts(att *sevsnp.Attestation) (*verify.Options, error) {
192192
ask, err := x509.ParseCertificate(att.CertificateChain.AskCert)
193193
if err != nil {
194-
return &verify.Options{}, fmt.Errorf("parsing VLEK certificate: %w", err)
194+
return nil, fmt.Errorf("parsing ASK certificate: %w", err)
195195
}
196196
ark, err := x509.ParseCertificate(att.CertificateChain.ArkCert)
197197
if err != nil {
198-
return &verify.Options{}, fmt.Errorf("parsing VLEK certificate: %w", err)
198+
return nil, fmt.Errorf("parsing ARK certificate: %w", err)
199199
}
200200

201201
verifyOpts := &verify.Options{

internal/attestation/azure/snp/validator.go

+30-16
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,11 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
116116
return nil, fmt.Errorf("parsing attestation report: %w", err)
117117
}
118118

119-
// ASK, as cached in joinservice or reported from THIM / KDS.
120-
ask, err := x509.ParseCertificate(att.CertificateChain.AskCert)
119+
verifyOpts, err := getVerifyOpts(att)
121120
if err != nil {
122-
return nil, fmt.Errorf("parsing ASK certificate: %w", err)
121+
return nil, fmt.Errorf("getting verify options: %w", err)
123122
}
124123

125-
verifyOpts := &verify.Options{
126-
TrustedRoots: map[string][]*trust.AMDRootCerts{
127-
"Milan": {
128-
{
129-
Product: "Milan",
130-
ProductCerts: &trust.ProductCerts{
131-
Ask: ask,
132-
Ark: trustedArk,
133-
},
134-
},
135-
},
136-
},
137-
}
138124
if err := v.attestationVerifier.SNPAttestation(att, verifyOpts); err != nil {
139125
return nil, fmt.Errorf("verifying SNP attestation: %w", err)
140126
}
@@ -252,3 +238,31 @@ type maaValidator interface {
252238
type hclAkValidator interface {
253239
Validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
254240
}
241+
242+
func getVerifyOpts(att *spb.Attestation) (*verify.Options, error) {
243+
// ASK, as cached in joinservice or reported from THIM / KDS.
244+
ask, err := x509.ParseCertificate(att.CertificateChain.AskCert)
245+
if err != nil {
246+
return nil, fmt.Errorf("parsing ASK certificate: %w", err)
247+
}
248+
ark, err := x509.ParseCertificate(att.CertificateChain.ArkCert)
249+
if err != nil {
250+
return nil, fmt.Errorf("parsing ARK certificate: %w", err)
251+
}
252+
253+
verifyOpts := &verify.Options{
254+
TrustedRoots: map[string][]*trust.AMDRootCerts{
255+
"Milan": {
256+
{
257+
Product: "Milan",
258+
ProductCerts: &trust.ProductCerts{
259+
Ask: ask,
260+
Ark: ark,
261+
},
262+
},
263+
},
264+
},
265+
}
266+
267+
return verifyOpts, nil
268+
}
-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2-
load("//bazel/go:go_test.bzl", "go_test")
32

43
go_library(
54
name = "snp",
@@ -24,27 +23,7 @@ go_library(
2423
"@com_github_google_go_sev_guest//validate",
2524
"@com_github_google_go_sev_guest//verify",
2625
"@com_github_google_go_sev_guest//verify/trust",
27-
"@com_github_google_go_tpm//legacy/tpm2",
2826
"@com_github_google_go_tpm_tools//client",
2927
"@com_github_google_go_tpm_tools//proto/attest",
3028
],
3129
)
32-
33-
go_test(
34-
name = "snp_test",
35-
srcs = ["validator_test.go"],
36-
embed = [":snp"],
37-
# keep
38-
gotags = select({
39-
"//bazel/settings:tpm_simulator_enabled": [],
40-
"//conditions:default": ["disable_tpm_simulator"],
41-
}),
42-
deps = [
43-
"//internal/attestation",
44-
"//internal/attestation/vtpm",
45-
"//internal/config",
46-
"@com_github_google_go_tpm_tools//proto/attest",
47-
"@com_github_stretchr_testify//assert",
48-
"@com_github_stretchr_testify//require",
49-
],
50-
)

internal/attestation/gcp/snp/issuer.go

+26-11
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func getAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
5858
// getInstanceInfo generates an extended SNP report, i.e. the report and any loaded certificates.
5959
// Report generation is triggered by sending ioctl syscalls to the SNP guest device, the AMD PSP generates the report.
6060
// The returned bytes will be written into the attestation document.
61-
func getInstanceInfo(_ context.Context, tpm io.ReadWriteCloser, extraData []byte) ([]byte, error) {
61+
func getInstanceInfo(_ context.Context, _ io.ReadWriteCloser, extraData []byte) ([]byte, error) {
6262
if len(extraData) > 64 {
6363
return nil, fmt.Errorf("extra data too long: %d, should be 64 bytes at most", len(extraData))
6464
}
@@ -76,7 +76,7 @@ func getInstanceInfo(_ context.Context, tpm io.ReadWriteCloser, extraData []byte
7676
return nil, fmt.Errorf("getting extended report: %w", err)
7777
}
7878

79-
vcek, err := pemEncodedVCEK(certs)
79+
vcek, certChain, err := parseSNPCertTable(certs)
8080
if err != nil {
8181
return nil, fmt.Errorf("parsing vcek: %w", err)
8282
}
@@ -89,6 +89,7 @@ func getInstanceInfo(_ context.Context, tpm io.ReadWriteCloser, extraData []byte
8989
raw, err := json.Marshal(snp.InstanceInfo{
9090
AttestationReport: report,
9191
ReportSigner: vcek,
92+
CertChain: certChain,
9293
GCP: gceInstanceInfo,
9394
})
9495
if err != nil {
@@ -124,30 +125,44 @@ func gceInstanceInfo() (*attest.GCEInstanceInfo, error) {
124125
}, nil
125126
}
126127

127-
// pemEncodedVCEK takes a marshalled SNP certificate table and returns the PEM-encoded VCEK certificate.
128+
// parseSNPCertTable takes a marshalled SNP certificate table and returns the PEM-encoded VCEK certificate and,
129+
// if present, the ASK of the SNP certificate chain.
128130
// AMD documentation on certificate tables can be found in section 4.1.8.1, revision 2.03 "SEV-ES Guest-Hypervisor Communication Block Standardization".
129131
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56421.pdf
130-
func pemEncodedVCEK(certs []byte) ([]byte, error) {
132+
func parseSNPCertTable(certs []byte) (vcekPEM []byte, certChain []byte, err error) {
131133
certTable := abi.CertTable{}
132134
if err := certTable.Unmarshal(certs); err != nil {
133-
return nil, fmt.Errorf("unmarshalling SNP certificate table: %w", err)
135+
return nil, nil, fmt.Errorf("unmarshalling SNP certificate table: %w", err)
134136
}
135137

136138
vcekRaw, err := certTable.GetByGUIDString(abi.VcekGUID)
137139
if err != nil {
138-
return nil, fmt.Errorf("getting VCEK certificate: %w", err)
140+
return nil, nil, fmt.Errorf("getting VCEK certificate: %w", err)
139141
}
140142

141143
// An optional check for certificate well-formedness. vcekRaw == cert.Raw.
142-
cert, err := x509.ParseCertificate(vcekRaw)
144+
vcek, err := x509.ParseCertificate(vcekRaw)
143145
if err != nil {
144-
return nil, fmt.Errorf("parsing certificate: %w", err)
146+
return nil, nil, fmt.Errorf("parsing certificate: %w", err)
145147
}
146148

147-
certPEM := pem.EncodeToMemory(&pem.Block{
149+
vcekPEM = pem.EncodeToMemory(&pem.Block{
148150
Type: "CERTIFICATE",
149-
Bytes: cert.Raw,
151+
Bytes: vcek.Raw,
150152
})
151153

152-
return certPEM, nil
154+
var askPEM []byte
155+
if askRaw, err := certTable.GetByGUIDString(abi.AskGUID); err == nil {
156+
ask, err := x509.ParseCertificate(askRaw)
157+
if err != nil {
158+
return nil, nil, fmt.Errorf("parsing ASK certificate: %w", err)
159+
}
160+
161+
askPEM = pem.EncodeToMemory(&pem.Block{
162+
Type: "CERTIFICATE",
163+
Bytes: ask.Raw,
164+
})
165+
}
166+
167+
return vcekPEM, askPEM, nil
153168
}

internal/attestation/gcp/snp/validator.go

+13-38
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package snp
99
import (
1010
"context"
1111
"crypto"
12-
"crypto/sha512"
1312
"crypto/x509"
1413
"encoding/json"
1514
"fmt"
@@ -27,7 +26,6 @@ import (
2726
"github.com/google/go-sev-guest/verify"
2827
"github.com/google/go-sev-guest/verify/trust"
2928
"github.com/google/go-tpm-tools/proto/attest"
30-
"github.com/google/go-tpm/legacy/tpm2"
3129
)
3230

3331
// Validator for GCP SEV-SNP / TPM attestation.
@@ -62,53 +60,30 @@ func NewValidator(cfg *config.GCPSEVSNP, log attestation.Logger) (*Validator, er
6260
v.Validator = vtpm.NewValidator(
6361
cfg.Measurements,
6462
v.getTrustedKey,
65-
v.validateCVM,
63+
func(_ vtpm.AttestationDocument, _ *attest.MachineState) error { return nil },
6664
log,
6765
)
6866
return v, nil
6967
}
7068

7169
// getTrustedKey returns TPM endorsement key provided through the GCE metadata API.
72-
func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error) {
70+
func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDocument, extraData []byte) (crypto.PublicKey, error) {
7371
ekPub, err := v.gceKeyGetter(ctx, attDoc, nil)
7472
if err != nil {
7573
return nil, fmt.Errorf("getting TPM endorsement key: %w", err)
7674
}
7775

78-
return ekPub, nil
79-
}
80-
81-
// validateCVM validates the SEV-SNP attestation document.
82-
func (v *Validator) validateCVM(attDoc vtpm.AttestationDocument, _ *attest.MachineState) error {
83-
pubArea, err := tpm2.DecodePublic(attDoc.Attestation.AkPub)
84-
if err != nil {
85-
return fmt.Errorf("decoding public area: %w", err)
86-
}
87-
88-
pubKey, err := pubArea.Key()
89-
if err != nil {
90-
return fmt.Errorf("getting public key: %w", err)
91-
}
92-
93-
akDigest, err := sha512sum(pubKey)
94-
if err != nil {
95-
return fmt.Errorf("calculating hash of attestation key: %w", err)
96-
}
97-
98-
if err := v.reportValidator.validate(attDoc, (*x509.Certificate)(&v.cfg.AMDSigningKey), (*x509.Certificate)(&v.cfg.AMDRootKey), akDigest, v.cfg, v.log); err != nil {
99-
return fmt.Errorf("validating SNP report: %w", err)
76+
if len(extraData) > 64 {
77+
return nil, fmt.Errorf("extra data too long: %d, should be 64 bytes at most", len(extraData))
10078
}
101-
return nil
102-
}
79+
extraData64 := make([]byte, 64)
80+
copy(extraData64, extraData)
10381

104-
// sha512sum PEM-encodes a public key and calculates the SHA512 hash of the encoded key.
105-
func sha512sum(key crypto.PublicKey) ([64]byte, error) {
106-
pub, err := x509.MarshalPKIXPublicKey(key)
107-
if err != nil {
108-
return [64]byte{}, fmt.Errorf("marshalling public key: %w", err)
82+
if err := v.reportValidator.validate(attDoc, (*x509.Certificate)(&v.cfg.AMDSigningKey), (*x509.Certificate)(&v.cfg.AMDRootKey), [64]byte(extraData64), v.cfg, v.log); err != nil {
83+
return nil, fmt.Errorf("validating SNP report: %w", err)
10984
}
11085

111-
return sha512.Sum512(pub), nil
86+
return ekPub, nil
11287
}
11388

11489
// snpReportValidator validates a given SNP report.
@@ -146,7 +121,7 @@ func (r *reportVerifierImpl) SnpAttestation(att *sevsnp.Attestation, opts *verif
146121
// validate the report by checking if it has a valid VCEK signature.
147122
// The certificate chain ARK -> ASK -> VCEK is also validated.
148123
// Checks that the report's userData matches the connection's userData.
149-
func (a *gcpValidator) validate(attestation vtpm.AttestationDocument, ask *x509.Certificate, ark *x509.Certificate, akDigest [64]byte, config *config.GCPSEVSNP, log attestation.Logger) error {
124+
func (a *gcpValidator) validate(attestation vtpm.AttestationDocument, ask *x509.Certificate, ark *x509.Certificate, reportData [64]byte, config *config.GCPSEVSNP, log attestation.Logger) error {
150125
var info snp.InstanceInfo
151126
if err := json.Unmarshal(attestation.InstanceInfo, &info); err != nil {
152127
return fmt.Errorf("unmarshalling instance info: %w", err)
@@ -170,7 +145,7 @@ func (a *gcpValidator) validate(attestation vtpm.AttestationDocument, ask *x509.
170145

171146
validateOpts := &validate.Options{
172147
// Check that the attestation key's digest is included in the report.
173-
ReportData: akDigest[:],
148+
ReportData: reportData[:],
174149
GuestPolicy: abi.SnpPolicy{
175150
Debug: false, // Debug means the VM can be decrypted by the host for debugging purposes and thus is not allowed.
176151
SMT: true, // Allow Simultaneous Multi-Threading (SMT). Normally, we would want to disable SMT
@@ -205,11 +180,11 @@ func (a *gcpValidator) validate(attestation vtpm.AttestationDocument, ask *x509.
205180
func getVerifyOpts(att *sevsnp.Attestation) (*verify.Options, error) {
206181
ask, err := x509.ParseCertificate(att.CertificateChain.AskCert)
207182
if err != nil {
208-
return &verify.Options{}, fmt.Errorf("parsing ASK certificate: %w", err)
183+
return nil, fmt.Errorf("parsing ASK certificate: %w", err)
209184
}
210185
ark, err := x509.ParseCertificate(att.CertificateChain.ArkCert)
211186
if err != nil {
212-
return &verify.Options{}, fmt.Errorf("parsing ARK certificate: %w", err)
187+
return nil, fmt.Errorf("parsing ARK certificate: %w", err)
213188
}
214189

215190
verifyOpts := &verify.Options{

0 commit comments

Comments
 (0)