Skip to content

Commit

Permalink
Merge pull request #36 from deeglaze/certfix
Browse files Browse the repository at this point in the history
Minor fixes for go-tpm-tools hardware testing
  • Loading branch information
deeglaze authored Feb 6, 2023
2 parents 0f7e438 + b757cb7 commit 08d1c7c
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 74 deletions.
24 changes: 15 additions & 9 deletions testing/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,22 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (
"Milan": {
{
Product: "Milan",
AskX509: sevTestDevice.Signer.Ask,
ArkX509: sevTestDevice.Signer.Ark,
ProductCerts: &trust.ProductCerts{
Ask: sevTestDevice.Signer.Ask,
Ark: sevTestDevice.Signer.Ark,
},
},
},
}
badSnpRoot := map[string][]*trust.AMDRootCerts{
"Milan": {
{
Product: "Milan",
// Backwards, oops
AskX509: sevTestDevice.Signer.Ark,
ArkX509: sevTestDevice.Signer.Ask,
ProductCerts: &trust.ProductCerts{
// Backwards, oops
Ask: sevTestDevice.Signer.Ark,
Ark: sevTestDevice.Signer.Ask,
},
},
},
}
Expand All @@ -76,10 +80,12 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (
// By flipping the ASK and ARK, we ensure that the attestation will never verify.
badSnpRoot[product] = []*trust.AMDRootCerts{{
Product: product,
ArkX509: rootCerts.AskX509,
AskX509: rootCerts.ArkX509,
AskSev: rootCerts.ArkSev,
ArkSev: rootCerts.AskSev,
ProductCerts: &trust.ProductCerts{
Ark: rootCerts.ProductCerts.Ask,
Ask: rootCerts.ProductCerts.Ark,
},
AskSev: rootCerts.ArkSev,
ArkSev: rootCerts.AskSev,
}}
}
return client, nil, badSnpRoot, test.GetKDS(tb)
Expand Down
35 changes: 24 additions & 11 deletions testing/fakekds.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ import (

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

// TestUseKDS returns whether tests should use the network to connect the live AMD Key Distribution
// service.
func TestUseKDS() bool {
return *testUseKDS
}

// The Milan product certificate bundle is only embedded for tests rather than in the main library
// since it's generally bad practice to embed certificates that can expire directly into a software
// project. Production uses should be providing their own certificates.
Expand All @@ -47,13 +53,16 @@ var internalKDSCache []byte
// with certificates cached in a protobuf.
type FakeKDS struct {
Certs *kpb.Certificates
// Two CERTIFICATE PEMs for ASK, then ARK.
RootBundle string
// Two CERTIFICATE PEMs for ASK, then ARK, per product
RootBundles map[string]string
}

// FakeKDSFromFile returns a FakeKDS from a path to a serialized fakekds.Certificates message.
func FakeKDSFromFile(path string) (*FakeKDS, error) {
result := &FakeKDS{Certs: &kpb.Certificates{}}
result := &FakeKDS{
Certs: &kpb.Certificates{},
RootBundles: map[string]string{"Milan": string(milanCerts)},
}

contents, err := os.ReadFile(path)
if os.IsNotExist(err) {
Expand Down Expand Up @@ -89,8 +98,8 @@ func FakeKDSFromSigner(signer *AmdSigner) (*FakeKDS, error) {
return nil, fmt.Errorf("could not encode root certificates: %v", err)
}
return &FakeKDS{
Certs: certs,
RootBundle: b.String(),
Certs: certs,
RootBundles: map[string]string{"Milan": b.String()},
}, nil
}

Expand All @@ -111,22 +120,23 @@ func (f *FakeKDS) Get(url string) ([]byte, error) {
// If a root cert request, return the embedded default root certs.
product, err := kds.ParseProductCertChainURL(url)
if err == nil {
if product == "Milan" {
return milanCerts, nil
bundle, ok := f.RootBundles[product]
if !ok {
return nil, fmt.Errorf("no embedded CA bundle for product %q", product)
}
return nil, fmt.Errorf("no embedded CA bundle for product %q", product)
return []byte(bundle), nil
}
vcek, err := kds.ParseVCEKCertURL(url)
if err != nil {
return nil, err
}
certs := FindChipTcbCerts(f.Certs, vcek.HWID)
if certs == nil {
return nil, fmt.Errorf("no certificate found at %q", url)
return nil, fmt.Errorf("no certificate found at %q (unknown HWID %v)", url, vcek.HWID)
}
certbytes, ok := certs[vcek.TCB]
if !ok {
return nil, fmt.Errorf("no certificate found at %q", url)
return nil, fmt.Errorf("no certificate found at %q (host present, bad TCB %v)", url, vcek.TCB)
}
return certbytes, nil
}
Expand All @@ -137,7 +147,10 @@ func GetKDS(t testing.TB) trust.HTTPSGetter {
if *testUseKDS {
return trust.DefaultHTTPSGetter()
}
fakeKds := &FakeKDS{Certs: &kpb.Certificates{}}
fakeKds := &FakeKDS{
Certs: &kpb.Certificates{},
RootBundles: map[string]string{"Milan": string(milanCerts)},
}
if err := proto.Unmarshal(internalKDSCache, fakeKds.Certs); err != nil {
t.Fatalf("could not unmarshal embedded FakeKDS file: %v", err)
}
Expand Down
12 changes: 9 additions & 3 deletions tools/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/google/go-sev-guest/tools/lib/cmdline"
"github.com/google/go-sev-guest/validate"
"github.com/google/go-sev-guest/verify"
"github.com/google/go-sev-guest/verify/testdata"
"github.com/google/go-sev-guest/verify/trust"
"github.com/google/logger"
"go.uber.org/multierr"
Expand Down Expand Up @@ -533,7 +534,10 @@ func main() {
if err != nil {
die(fmt.Errorf("could not read %q: %v", *testKdsFile, err))
}
kds := &testing.FakeKDS{Certs: &kpb.Certificates{}}
kds := &testing.FakeKDS{
Certs: &kpb.Certificates{},
RootBundles: map[string]string{"Milan": string(testdata.MilanBytes)},
}
sopts.Getter = kds
if err := proto.Unmarshal(b, kds.Certs); err != nil {
die(fmt.Errorf("could not unmarshal KDS database: %v", err))
Expand All @@ -547,10 +551,12 @@ func main() {
if err == nil {
return false
}
if errors.As(err, &verify.AttestationRecreationErr{}) {
var certNetworkErr *trust.AttestationRecreationErr
var crlNetworkErr *verify.CRLUnavailableErr
if errors.As(err, &certNetworkErr) {
exitCode = exitCerts
return true
} else if errors.As(err, &verify.CRLUnavailableErr{}) {
} else if errors.As(err, &crlNetworkErr) {
exitCode = exitCrl
return true
}
Expand Down
126 changes: 112 additions & 14 deletions verify/trust/trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,24 @@ var (
// expiration dates is at https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
//go:embed ask_ark_milan.sevcert
askArkMilanBytes []byte

// A cache of product certificate KDS results per product.
prodCacheMu sync.Mutex
productCertCache map[string]*ProductCerts
)

// ProductCerts contains the root key and signing key devoted to a given product line.
type ProductCerts struct {
Ask *x509.Certificate
Ark *x509.Certificate
}

// AMDRootCerts encapsulates the certificates that represent root of trust in AMD.
type AMDRootCerts struct {
// Product is the expected CPU product name, e.g., Milan, Turin, Genoa.
Product string
// AskX509 is an X.509 certificate for the AMD SEV signing key (ASK)
AskX509 *x509.Certificate
// ArkX509 is an X.509 certificate for the AMD root key (ARK).
ArkX509 *x509.Certificate
// ProductCerts contains the root key and signing key devoted to a given product line.
ProductCerts *ProductCerts
// AskSev is the AMD certificate representation of the AMD signing key that certifies
// versioned chip endoresement keys. If present, the information must match AskX509.
AskSev *abi.AskCert
Expand All @@ -71,6 +79,16 @@ type HTTPSGetter interface {
Get(url string) ([]byte, error)
}

// AttestationRecreationErr represents a problem with fetching or interpreting associated
// certificates for a given attestation report. This is typically due to network unreliability.
type AttestationRecreationErr struct {
Msg string
}

func (e *AttestationRecreationErr) Error() string {
return e.Msg
}

// SimpleHTTPSGetter implements the HTTPSGetter interface with http.Get.
type SimpleHTTPSGetter struct{}

Expand Down Expand Up @@ -149,26 +167,26 @@ func (r *AMDRootCerts) Unmarshal(data []byte) error {
return nil
}

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

arkCert, err := x509.ParseCertificate(ark)
if err != nil {
logger.Errorf("could not parse ARK certificate: %v", err)
}
r.ArkX509 = arkCert
r.Ark = arkCert
return nil
}

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

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

// X509Options returns the ASK and ARK as the only intermediate and root certificates of an x509
// verification options object, or nil if either key's x509 certificate is not present in r.
func (r *AMDRootCerts) X509Options() *x509.VerifyOptions {
if r.AskX509 == nil || r.ArkX509 == nil {
func (r *ProductCerts) X509Options() *x509.VerifyOptions {
if r.Ask == nil || r.Ark == nil {
return nil
}
roots := x509.NewCertPool()
roots.AddCert(r.ArkX509)
roots.AddCert(r.Ark)
intermediates := x509.NewCertPool()
intermediates.AddCert(r.AskX509)
intermediates.AddCert(r.Ask)
return &x509.VerifyOptions{Roots: roots, Intermediates: intermediates}
}

// ClearProductCertCache clears the product certificate cache. This is useful for testing with
// multiple roots of trust.
func ClearProductCertCache() {
prodCacheMu.Lock()
productCertCache = nil
prodCacheMu.Unlock()
}

// GetProductChain returns the ASK and ARK certificates of the given product, either from getter
// or from a cache of the results from the last successful call.
func GetProductChain(product string, getter HTTPSGetter) (*ProductCerts, error) {
if productCertCache == nil {
prodCacheMu.Lock()
productCertCache = make(map[string]*ProductCerts)
prodCacheMu.Unlock()
}
result, ok := productCertCache[product]
if !ok {
logger.Infof("Getting product cert chain for %s", product)
askark, err := getter.Get(kds.ProductCertChainURL(product))
if err != nil {
return nil, &AttestationRecreationErr{
Msg: fmt.Sprintf("could not download ASK and ARK certificates: %v", err),
}
}
logger.Infof("Product chain in %s", string(askark))
ask, ark, err := kds.ParseProductCertChain(askark)
if err != nil {
// Treat a bad parse as a network error since it's likely due to an incomplete transfer.
return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse root cert_chain: %v", err)}
}
askCert, err := x509.ParseCertificate(ask)
if err != nil {
return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse ASK cert: %v", err)}
}
arkCert, err := x509.ParseCertificate(ark)
if err != nil {
return nil, &AttestationRecreationErr{Msg: fmt.Sprintf("could not parse ARK cert: %v", err)}
}
result = &ProductCerts{Ask: askCert, Ark: arkCert}
prodCacheMu.Lock()
productCertCache[product] = result
prodCacheMu.Unlock()
}
return result, nil
}

// Forward all the ProductCerts operations from the AMDRootCerts struct to follow the
// Law of Demeter.

// FromDER populates the AMDRootCerts from DER-formatted certificates for both the ASK and the ARK.
func (r *AMDRootCerts) FromDER(ask []byte, ark []byte) error {
r.ProductCerts = &ProductCerts{}
return r.ProductCerts.FromDER(ask, ark)
}

// FromKDSCertBytes populates r's AskX509 and ArkX509 certificates from the two PEM-encoded
// certificates in data. This is the format the Key Distribution Service (KDS) uses, e.g.,
// https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
func (r *AMDRootCerts) FromKDSCertBytes(data []byte) error {
r.ProductCerts = &ProductCerts{}
return r.ProductCerts.FromKDSCertBytes(data)
}

// FromKDSCert populates r's AskX509 and ArkX509 certificates from the certificate format AMD's Key
// Distribution Service (KDS) uses, e.g., https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
func (r *AMDRootCerts) FromKDSCert(path string) error {
r.ProductCerts = &ProductCerts{}
return r.ProductCerts.FromKDSCert(path)
}

// X509Options returns the ASK and ARK as the only intermediate and root certificates of an x509
// verification options object, or nil if either key's x509 certificate is not present in r.
func (r *AMDRootCerts) X509Options() *x509.VerifyOptions {
if r.ProductCerts == nil {
return nil
}
return r.ProductCerts.X509Options()
}

// Parse ASK, ARK certificates from the embedded AMD certificate file.
func init() {
milanCerts := new(AMDRootCerts)
Expand Down
Loading

0 comments on commit 08d1c7c

Please sign in to comment.