Skip to content

Commit 08d1c7c

Browse files
authored
Merge pull request #36 from deeglaze/certfix
Minor fixes for go-tpm-tools hardware testing
2 parents 0f7e438 + b757cb7 commit 08d1c7c

File tree

6 files changed

+202
-74
lines changed

6 files changed

+202
-74
lines changed

testing/client/client.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,22 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (
4545
"Milan": {
4646
{
4747
Product: "Milan",
48-
AskX509: sevTestDevice.Signer.Ask,
49-
ArkX509: sevTestDevice.Signer.Ark,
48+
ProductCerts: &trust.ProductCerts{
49+
Ask: sevTestDevice.Signer.Ask,
50+
Ark: sevTestDevice.Signer.Ark,
51+
},
5052
},
5153
},
5254
}
5355
badSnpRoot := map[string][]*trust.AMDRootCerts{
5456
"Milan": {
5557
{
5658
Product: "Milan",
57-
// Backwards, oops
58-
AskX509: sevTestDevice.Signer.Ark,
59-
ArkX509: sevTestDevice.Signer.Ask,
59+
ProductCerts: &trust.ProductCerts{
60+
// Backwards, oops
61+
Ask: sevTestDevice.Signer.Ark,
62+
Ark: sevTestDevice.Signer.Ask,
63+
},
6064
},
6165
},
6266
}
@@ -76,10 +80,12 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (
7680
// By flipping the ASK and ARK, we ensure that the attestation will never verify.
7781
badSnpRoot[product] = []*trust.AMDRootCerts{{
7882
Product: product,
79-
ArkX509: rootCerts.AskX509,
80-
AskX509: rootCerts.ArkX509,
81-
AskSev: rootCerts.ArkSev,
82-
ArkSev: rootCerts.AskSev,
83+
ProductCerts: &trust.ProductCerts{
84+
Ark: rootCerts.ProductCerts.Ask,
85+
Ask: rootCerts.ProductCerts.Ark,
86+
},
87+
AskSev: rootCerts.ArkSev,
88+
ArkSev: rootCerts.AskSev,
8389
}}
8490
}
8591
return client, nil, badSnpRoot, test.GetKDS(tb)

testing/fakekds.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ import (
3333

3434
var testUseKDS = flag.Bool("test_use_kds", false, "If true, tests will attempt to retrieve certificates from AMD KDS")
3535

36+
// TestUseKDS returns whether tests should use the network to connect the live AMD Key Distribution
37+
// service.
38+
func TestUseKDS() bool {
39+
return *testUseKDS
40+
}
41+
3642
// The Milan product certificate bundle is only embedded for tests rather than in the main library
3743
// since it's generally bad practice to embed certificates that can expire directly into a software
3844
// project. Production uses should be providing their own certificates.
@@ -47,13 +53,16 @@ var internalKDSCache []byte
4753
// with certificates cached in a protobuf.
4854
type FakeKDS struct {
4955
Certs *kpb.Certificates
50-
// Two CERTIFICATE PEMs for ASK, then ARK.
51-
RootBundle string
56+
// Two CERTIFICATE PEMs for ASK, then ARK, per product
57+
RootBundles map[string]string
5258
}
5359

5460
// FakeKDSFromFile returns a FakeKDS from a path to a serialized fakekds.Certificates message.
5561
func FakeKDSFromFile(path string) (*FakeKDS, error) {
56-
result := &FakeKDS{Certs: &kpb.Certificates{}}
62+
result := &FakeKDS{
63+
Certs: &kpb.Certificates{},
64+
RootBundles: map[string]string{"Milan": string(milanCerts)},
65+
}
5766

5867
contents, err := os.ReadFile(path)
5968
if os.IsNotExist(err) {
@@ -89,8 +98,8 @@ func FakeKDSFromSigner(signer *AmdSigner) (*FakeKDS, error) {
8998
return nil, fmt.Errorf("could not encode root certificates: %v", err)
9099
}
91100
return &FakeKDS{
92-
Certs: certs,
93-
RootBundle: b.String(),
101+
Certs: certs,
102+
RootBundles: map[string]string{"Milan": b.String()},
94103
}, nil
95104
}
96105

@@ -111,22 +120,23 @@ func (f *FakeKDS) Get(url string) ([]byte, error) {
111120
// If a root cert request, return the embedded default root certs.
112121
product, err := kds.ParseProductCertChainURL(url)
113122
if err == nil {
114-
if product == "Milan" {
115-
return milanCerts, nil
123+
bundle, ok := f.RootBundles[product]
124+
if !ok {
125+
return nil, fmt.Errorf("no embedded CA bundle for product %q", product)
116126
}
117-
return nil, fmt.Errorf("no embedded CA bundle for product %q", product)
127+
return []byte(bundle), nil
118128
}
119129
vcek, err := kds.ParseVCEKCertURL(url)
120130
if err != nil {
121131
return nil, err
122132
}
123133
certs := FindChipTcbCerts(f.Certs, vcek.HWID)
124134
if certs == nil {
125-
return nil, fmt.Errorf("no certificate found at %q", url)
135+
return nil, fmt.Errorf("no certificate found at %q (unknown HWID %v)", url, vcek.HWID)
126136
}
127137
certbytes, ok := certs[vcek.TCB]
128138
if !ok {
129-
return nil, fmt.Errorf("no certificate found at %q", url)
139+
return nil, fmt.Errorf("no certificate found at %q (host present, bad TCB %v)", url, vcek.TCB)
130140
}
131141
return certbytes, nil
132142
}
@@ -137,7 +147,10 @@ func GetKDS(t testing.TB) trust.HTTPSGetter {
137147
if *testUseKDS {
138148
return trust.DefaultHTTPSGetter()
139149
}
140-
fakeKds := &FakeKDS{Certs: &kpb.Certificates{}}
150+
fakeKds := &FakeKDS{
151+
Certs: &kpb.Certificates{},
152+
RootBundles: map[string]string{"Milan": string(milanCerts)},
153+
}
141154
if err := proto.Unmarshal(internalKDSCache, fakeKds.Certs); err != nil {
142155
t.Fatalf("could not unmarshal embedded FakeKDS file: %v", err)
143156
}

tools/check/check.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/google/go-sev-guest/tools/lib/cmdline"
3535
"github.com/google/go-sev-guest/validate"
3636
"github.com/google/go-sev-guest/verify"
37+
"github.com/google/go-sev-guest/verify/testdata"
3738
"github.com/google/go-sev-guest/verify/trust"
3839
"github.com/google/logger"
3940
"go.uber.org/multierr"
@@ -533,7 +534,10 @@ func main() {
533534
if err != nil {
534535
die(fmt.Errorf("could not read %q: %v", *testKdsFile, err))
535536
}
536-
kds := &testing.FakeKDS{Certs: &kpb.Certificates{}}
537+
kds := &testing.FakeKDS{
538+
Certs: &kpb.Certificates{},
539+
RootBundles: map[string]string{"Milan": string(testdata.MilanBytes)},
540+
}
537541
sopts.Getter = kds
538542
if err := proto.Unmarshal(b, kds.Certs); err != nil {
539543
die(fmt.Errorf("could not unmarshal KDS database: %v", err))
@@ -547,10 +551,12 @@ func main() {
547551
if err == nil {
548552
return false
549553
}
550-
if errors.As(err, &verify.AttestationRecreationErr{}) {
554+
var certNetworkErr *trust.AttestationRecreationErr
555+
var crlNetworkErr *verify.CRLUnavailableErr
556+
if errors.As(err, &certNetworkErr) {
551557
exitCode = exitCerts
552558
return true
553-
} else if errors.As(err, &verify.CRLUnavailableErr{}) {
559+
} else if errors.As(err, &crlNetworkErr) {
554560
exitCode = exitCrl
555561
return true
556562
}

verify/trust/trust.go

Lines changed: 112 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,24 @@ var (
4242
// expiration dates is at https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
4343
//go:embed ask_ark_milan.sevcert
4444
askArkMilanBytes []byte
45+
46+
// A cache of product certificate KDS results per product.
47+
prodCacheMu sync.Mutex
48+
productCertCache map[string]*ProductCerts
4549
)
4650

51+
// ProductCerts contains the root key and signing key devoted to a given product line.
52+
type ProductCerts struct {
53+
Ask *x509.Certificate
54+
Ark *x509.Certificate
55+
}
56+
4757
// AMDRootCerts encapsulates the certificates that represent root of trust in AMD.
4858
type AMDRootCerts struct {
4959
// Product is the expected CPU product name, e.g., Milan, Turin, Genoa.
5060
Product string
51-
// AskX509 is an X.509 certificate for the AMD SEV signing key (ASK)
52-
AskX509 *x509.Certificate
53-
// ArkX509 is an X.509 certificate for the AMD root key (ARK).
54-
ArkX509 *x509.Certificate
61+
// ProductCerts contains the root key and signing key devoted to a given product line.
62+
ProductCerts *ProductCerts
5563
// AskSev is the AMD certificate representation of the AMD signing key that certifies
5664
// versioned chip endoresement keys. If present, the information must match AskX509.
5765
AskSev *abi.AskCert
@@ -71,6 +79,16 @@ type HTTPSGetter interface {
7179
Get(url string) ([]byte, error)
7280
}
7381

82+
// AttestationRecreationErr represents a problem with fetching or interpreting associated
83+
// certificates for a given attestation report. This is typically due to network unreliability.
84+
type AttestationRecreationErr struct {
85+
Msg string
86+
}
87+
88+
func (e *AttestationRecreationErr) Error() string {
89+
return e.Msg
90+
}
91+
7492
// SimpleHTTPSGetter implements the HTTPSGetter interface with http.Get.
7593
type SimpleHTTPSGetter struct{}
7694

@@ -149,26 +167,26 @@ func (r *AMDRootCerts) Unmarshal(data []byte) error {
149167
return nil
150168
}
151169

152-
// FromDER populates the AMDRootCerts from DER-formatted certificates for both the ASK and the ARK.
153-
func (r *AMDRootCerts) FromDER(ask []byte, ark []byte) error {
170+
// FromDER populates the ProductCerts from DER-formatted certificates for both the ASK and the ARK.
171+
func (r *ProductCerts) FromDER(ask []byte, ark []byte) error {
154172
askCert, err := x509.ParseCertificate(ask)
155173
if err != nil {
156174
return fmt.Errorf("could not parse ASK certificate: %v", err)
157175
}
158-
r.AskX509 = askCert
176+
r.Ask = askCert
159177

160178
arkCert, err := x509.ParseCertificate(ark)
161179
if err != nil {
162180
logger.Errorf("could not parse ARK certificate: %v", err)
163181
}
164-
r.ArkX509 = arkCert
182+
r.Ark = arkCert
165183
return nil
166184
}
167185

168186
// FromKDSCertBytes populates r's AskX509 and ArkX509 certificates from the two PEM-encoded
169187
// certificates in data. This is the format the Key Distribution Service (KDS) uses, e.g.,
170188
// https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
171-
func (r *AMDRootCerts) FromKDSCertBytes(data []byte) error {
189+
func (r *ProductCerts) FromKDSCertBytes(data []byte) error {
172190
ask, ark, err := kds.ParseProductCertChain(data)
173191
if err != nil {
174192
return err
@@ -178,7 +196,7 @@ func (r *AMDRootCerts) FromKDSCertBytes(data []byte) error {
178196

179197
// FromKDSCert populates r's AskX509 and ArkX509 certificates from the certificate format AMD's Key
180198
// Distribution Service (KDS) uses, e.g., https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
181-
func (r *AMDRootCerts) FromKDSCert(path string) error {
199+
func (r *ProductCerts) FromKDSCert(path string) error {
182200
certBytes, err := os.ReadFile(path)
183201
if err != nil {
184202
return err
@@ -188,17 +206,97 @@ func (r *AMDRootCerts) FromKDSCert(path string) error {
188206

189207
// X509Options returns the ASK and ARK as the only intermediate and root certificates of an x509
190208
// verification options object, or nil if either key's x509 certificate is not present in r.
191-
func (r *AMDRootCerts) X509Options() *x509.VerifyOptions {
192-
if r.AskX509 == nil || r.ArkX509 == nil {
209+
func (r *ProductCerts) X509Options() *x509.VerifyOptions {
210+
if r.Ask == nil || r.Ark == nil {
193211
return nil
194212
}
195213
roots := x509.NewCertPool()
196-
roots.AddCert(r.ArkX509)
214+
roots.AddCert(r.Ark)
197215
intermediates := x509.NewCertPool()
198-
intermediates.AddCert(r.AskX509)
216+
intermediates.AddCert(r.Ask)
199217
return &x509.VerifyOptions{Roots: roots, Intermediates: intermediates}
200218
}
201219

220+
// ClearProductCertCache clears the product certificate cache. This is useful for testing with
221+
// multiple roots of trust.
222+
func ClearProductCertCache() {
223+
prodCacheMu.Lock()
224+
productCertCache = nil
225+
prodCacheMu.Unlock()
226+
}
227+
228+
// GetProductChain returns the ASK and ARK certificates of the given product, either from getter
229+
// or from a cache of the results from the last successful call.
230+
func GetProductChain(product string, getter HTTPSGetter) (*ProductCerts, error) {
231+
if productCertCache == nil {
232+
prodCacheMu.Lock()
233+
productCertCache = make(map[string]*ProductCerts)
234+
prodCacheMu.Unlock()
235+
}
236+
result, ok := productCertCache[product]
237+
if !ok {
238+
logger.Infof("Getting product cert chain for %s", product)
239+
askark, err := getter.Get(kds.ProductCertChainURL(product))
240+
if err != nil {
241+
return nil, &AttestationRecreationErr{
242+
Msg: fmt.Sprintf("could not download ASK and ARK certificates: %v", err),
243+
}
244+
}
245+
logger.Infof("Product chain in %s", string(askark))
246+
ask, ark, err := kds.ParseProductCertChain(askark)
247+
if err != nil {
248+
// Treat a bad parse as a network error since it's likely due to an incomplete transfer.
249+
return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse root cert_chain: %v", err)}
250+
}
251+
askCert, err := x509.ParseCertificate(ask)
252+
if err != nil {
253+
return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse ASK cert: %v", err)}
254+
}
255+
arkCert, err := x509.ParseCertificate(ark)
256+
if err != nil {
257+
return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse ARK cert: %v", err)}
258+
}
259+
result = &ProductCerts{Ask: askCert, Ark: arkCert}
260+
prodCacheMu.Lock()
261+
productCertCache[product] = result
262+
prodCacheMu.Unlock()
263+
}
264+
return result, nil
265+
}
266+
267+
// Forward all the ProductCerts operations from the AMDRootCerts struct to follow the
268+
// Law of Demeter.
269+
270+
// FromDER populates the AMDRootCerts from DER-formatted certificates for both the ASK and the ARK.
271+
func (r *AMDRootCerts) FromDER(ask []byte, ark []byte) error {
272+
r.ProductCerts = &ProductCerts{}
273+
return r.ProductCerts.FromDER(ask, ark)
274+
}
275+
276+
// FromKDSCertBytes populates r's AskX509 and ArkX509 certificates from the two PEM-encoded
277+
// certificates in data. This is the format the Key Distribution Service (KDS) uses, e.g.,
278+
// https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
279+
func (r *AMDRootCerts) FromKDSCertBytes(data []byte) error {
280+
r.ProductCerts = &ProductCerts{}
281+
return r.ProductCerts.FromKDSCertBytes(data)
282+
}
283+
284+
// FromKDSCert populates r's AskX509 and ArkX509 certificates from the certificate format AMD's Key
285+
// Distribution Service (KDS) uses, e.g., https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
286+
func (r *AMDRootCerts) FromKDSCert(path string) error {
287+
r.ProductCerts = &ProductCerts{}
288+
return r.ProductCerts.FromKDSCert(path)
289+
}
290+
291+
// X509Options returns the ASK and ARK as the only intermediate and root certificates of an x509
292+
// verification options object, or nil if either key's x509 certificate is not present in r.
293+
func (r *AMDRootCerts) X509Options() *x509.VerifyOptions {
294+
if r.ProductCerts == nil {
295+
return nil
296+
}
297+
return r.ProductCerts.X509Options()
298+
}
299+
202300
// Parse ASK, ARK certificates from the embedded AMD certificate file.
203301
func init() {
204302
milanCerts := new(AMDRootCerts)

0 commit comments

Comments
 (0)