-
Notifications
You must be signed in to change notification settings - Fork 52
/
snp.go
260 lines (225 loc) · 8.88 KB
/
snp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package SNP provides types shared by SNP-based attestation implementations.
// It ensures all issuers provide the same types to the verify command.
package snp
import (
"bytes"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/attestation"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/kds"
spb "github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/verify/trust"
)
// Product returns the SEV product info currently supported by Constellation's SNP attestation.
func Product() *spb.SevProduct {
// sevProduct is the product info of the SEV platform as reported through CPUID[EAX=1].
// It may become necessary in the future to differentiate among CSP vendors.
return &spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, Stepping: 0} // Milan-B0
}
// InstanceInfo contains the necessary information to establish trust in a SNP CVM.
type InstanceInfo struct {
// 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.
CertChain []byte
// AttestationReport is the attestation report from the vTPM (NVRAM) of the CVM.
AttestationReport []byte
Azure *AzureInstanceInfo
}
// AzureInstanceInfo contains Azure specific information related to SNP attestation.
type AzureInstanceInfo struct {
// RuntimeData is the Azure runtime data from the vTPM (NVRAM) of the CVM.
RuntimeData []byte
// MAAToken is the token of the MAA for the attestation report, used as a fallback
// if the IDKeyDigest cannot be verified.
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(getter trust.HTTPSGetter,
fallbackCerts CertificateChain, logger attestation.Logger,
) (*spb.Attestation, error) {
report, err := abi.ReportToProto(a.AttestationReport)
if err != nil {
return nil, fmt.Errorf("converting report to proto: %w", err)
}
productName := kds.ProductString(Product())
att := &spb.Attestation{
Report: report,
CertificateChain: &spb.CertificateChain{},
Product: Product(),
}
// Add VCEK/VLEK to attestation object.
signingInfo, err := a.addReportSigner(att, report, productName, getter, logger)
if err != nil {
return nil, fmt.Errorf("adding report signer: %w", err)
}
// If the certificate chain from THIM is present, parse it and format it.
ask, ark, err := a.ParseCertChain()
if err != nil {
logger.Warnf("Error parsing certificate chain: %v", err)
}
if ask != nil {
logger.Infof("Using ASK certificate from Azure THIM")
att.CertificateChain.AskCert = ask.Raw
}
if ark != nil {
logger.Infof("Using ARK certificate from Azure THIM")
att.CertificateChain.ArkCert = ark.Raw
}
// If a cached ASK or an ARK from the Constellation config is present, use it.
if att.CertificateChain.AskCert == nil && fallbackCerts.ask != nil {
logger.Infof("Using cached ASK certificate")
att.CertificateChain.AskCert = fallbackCerts.ask.Raw
}
if att.CertificateChain.ArkCert == nil && fallbackCerts.ark != nil {
logger.Infof("Using ARK certificate from %s", constants.ConfigFilename)
att.CertificateChain.ArkCert = fallbackCerts.ark.Raw
}
// Otherwise, retrieve it from AMD KDS.
if att.CertificateChain.AskCert == nil || att.CertificateChain.ArkCert == nil {
logger.Infof(
"Certificate chain not fully present (ARK present: %t, ASK present: %t), falling back to retrieving it from AMD KDS",
(att.CertificateChain.ArkCert != nil),
(att.CertificateChain.AskCert != nil),
)
kdsCertChain, err := trust.GetProductChain(productName, signingInfo, getter)
if err != nil {
return nil, fmt.Errorf("retrieving certificate chain from AMD KDS: %w", err)
}
if att.CertificateChain.AskCert == nil && kdsCertChain.Ask != nil {
logger.Infof("Using ASK certificate from AMD KDS")
att.CertificateChain.AskCert = kdsCertChain.Ask.Raw
}
if att.CertificateChain.ArkCert == nil && kdsCertChain.Ask != nil {
logger.Infof("Using ARK certificate from AMD KDS")
att.CertificateChain.ArkCert = kdsCertChain.Ark.Raw
}
}
return att, nil
}
// CertificateChain stores an AMD signing key (ASK) and AMD root key (ARK) certificate.
type CertificateChain struct {
ask *x509.Certificate
ark *x509.Certificate
}
// NewCertificateChain returns a new CertificateChain with the given ASK and ARK certificates.
func NewCertificateChain(ask, ark *x509.Certificate) CertificateChain {
return CertificateChain{
ask: ask,
ark: ark,
}
}
// ParseCertChain parses the certificate chain from the instanceInfo into x509-formatted ASK and ARK certificates.
// If less than 2 certificates are present, only the present certificate is returned.
// If more than 2 certificates are present, an error is returned.
func (a *InstanceInfo) ParseCertChain() (ask, ark *x509.Certificate, retErr error) {
rest := bytes.TrimSpace(a.CertChain)
i := 1
var block *pem.Block
for block, rest = pem.Decode(rest); block != nil; block, rest = pem.Decode(rest) {
if i > 2 {
retErr = fmt.Errorf("parse certificate %d: more than 2 certificates in chain", i)
return
}
if block.Type != "CERTIFICATE" {
retErr = fmt.Errorf("parse certificate %d: expected PEM block type 'CERTIFICATE', got '%s'", i, block.Type)
return
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
retErr = fmt.Errorf("parse certificate %d: %w", i, err)
return
}
// 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", "SEV-VLEK-Milan":
ask = cert
case "ARK-Milan":
ark = cert
default:
retErr = fmt.Errorf("parse certificate %d: unexpected subject CN %s", i, cert.Subject.CommonName)
return
}
i++
}
switch {
case i == 1:
retErr = fmt.Errorf("no PEM blocks found")
case len(rest) != 0:
retErr = fmt.Errorf("remaining PEM block is not a valid certificate: %s", rest)
}
return
}
// 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.
return nil, nil
}
block, rest := pem.Decode(newlinesTrimmed)
if block == nil {
return nil, fmt.Errorf("no PEM blocks found")
}
if len(rest) != 0 {
return nil, fmt.Errorf("received more data than expected")
}
if block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("expected PEM block type 'CERTIFICATE', got '%s'", block.Type)
}
reportSigner, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parsing VCEK certificate: %w", err)
}
return reportSigner, nil
}