From 5db2aa3d4c515503a48a15f396893cb1df3b8697 Mon Sep 17 00:00:00 2001 From: Dionna Glaze Date: Tue, 16 May 2023 23:39:14 +0000 Subject: [PATCH] Add support for validating VLEK certificates This changes the AuthorKeyEn report field to a better-named SignerInfo. The interpretation of this field selects which key the fake signer will use when signing reports. The VLEK certificate extensions are now checked against AMD's VLEK specification. The difference is VLEK certs are like VCEK certs, except the HWID extension is swapped with a CSP_ID extension. Tests have been updated to reflect these changes, including new VLEK-specific test cases. Signed-off-by: Dionna Glaze --- abi/abi.go | 129 ++++- abi/abi_test.go | 42 +- client/client_test.go | 8 +- kds/kds.go | 440 +++++++++++++----- kds/kds_test.go | 59 ++- proto/check.proto | 2 +- proto/check/check.pb.go | 2 +- proto/sevsnp.proto | 12 +- proto/sevsnp/sevsnp.pb.go | 182 ++++---- testing/client/client.go | 18 +- testing/fake_certs.go | 224 +++++++-- testing/fake_certs_test.go | 20 +- testing/fakekds.go | 68 ++- testing/test_cases.go | 68 ++- tools/check/check.go | 7 +- tools/check/check_test.go | 2 +- validate/validate.go | 97 ++-- validate/validate_test.go | 25 +- .../testdata/milanvlek.testcer | 65 +-- verify/testdata/testdata.go | 9 +- verify/trust/trust.go | 55 ++- verify/verify.go | 242 ++++++---- verify/verify_test.go | 135 ++++-- 23 files changed, 1373 insertions(+), 538 deletions(-) rename testing/milan.pem => verify/testdata/milanvlek.testcer (55%) diff --git a/abi/abi.go b/abi/abi.go index bfeef84..6bb4d37 100644 --- a/abi/abi.go +++ b/abi/abi.go @@ -102,10 +102,13 @@ const ( VcekGUID = "63da758d-e664-4564-adc5-f4b93be8accd" // VlekGUID is the Versioned Loaded Endorsement Key GUID VlekGUID = "a8074bc2-a25a-483e-aae6-39c045a0b8a1" - // AskGUID is the AMD signing Key GUID + // AskGUID is the AMD signing Key GUID. Used for the ASVK as well. AskGUID = "4ab7b379-bbac-4fe4-a02f-05aef327c782" // ArkGUID is the AMD Root Key GUID ArkGUID = "c0b406a4-a803-4952-9743-3fb6014cd0ae" + // AsvkGUID may not be defined, but we'd like it to be, so that + // a single machine can use both VCEK and VLEK report signing. + AsvkGUID = "00000000-0000-0000-0000-000000000000" // ExpectedReportVersion is set by the SNP API specification // https://www.amd.com/system/files/TechDocs/56860.pdf @@ -184,9 +187,8 @@ func ParseSnpPolicy(guestPolicy uint64) (SnpPolicy, error) { if guestPolicy&uint64(1<> 8) & 0xff) @@ -293,6 +295,14 @@ func mbz(data []uint8, lo, hi int) error { return nil } +// Checks a must-be-zero range of a uint64 between bits hi down to lo inclusive. +func mbz64(data uint64, base string, hi, lo int) error { + if (data>>lo)&((1<<(hi-lo+1))-1) != 0 { + return fmt.Errorf("mbz range %s[0x%x:0x%x] not all zero: %x", base, lo, hi, data) + } + return nil +} + // ReportToSignatureDER returns the signature component of an attestation report in DER format for // use in x509 verification. func ReportToSignatureDER(report []byte) ([]byte, error) { @@ -335,6 +345,86 @@ func SignatureAlgo(report []byte) uint32 { return binary.LittleEndian.Uint32(signatureAlgoSlice(report)) } +// ReportSigner represents which kind of key is expected to have signed the attestation report +type ReportSigner uint8 + +const ( + // VcekReportSigner is the SIGNING_KEY value for if the VCEK signed the attestation report. + VcekReportSigner ReportSigner = iota + // VlekReportSigner is the SIGNING_KEY value for if the VLEK signed the attestation report. + VlekReportSigner + endorseReserved2 + endorseReserved3 + endorseReserved4 + endorseReserved5 + endorseReserved6 + // NoneReportSigner is the SIGNING_KEY value for if the attestation report is not signed. + NoneReportSigner +) + +// SignerInfo represents information about the signing circumstances for the attestation report. +type SignerInfo struct { + // SigningKey represents kind of key by which a report was signed. + SigningKey ReportSigner + // MaskChipKey is true if the host chose to enable CHIP_ID masking, to cause the report's CHIP_ID + // to be all zeros. + MaskChipKey bool + // AuthorKeyEn is true if the VM is launched with an IDBLOCK that includes an author key. + AuthorKeyEn bool +} + +// String returns a ReportSigner string rendering. +func (k ReportSigner) String() string { + switch k { + case VcekReportSigner: + return "VCEK" + case VlekReportSigner: + return "VLEK" + case NoneReportSigner: + return "None" + default: + return fmt.Sprintf("UNKNOWN(%d)", byte(k)) + } +} + +// ParseSignerInfo interprets report[0x48:0x4c] into its component pieces and errors +// on non-zero mbz fields. +func ParseSignerInfo(signerInfo uint32) (result SignerInfo, err error) { + info64 := uint64(signerInfo) + if err = mbz64(info64, "data[0x48:0x4C]", 31, 5); err != nil { + return result, err + } + result.SigningKey = ReportSigner((signerInfo >> 2) & 7) + if result.SigningKey > VlekReportSigner && result.SigningKey < NoneReportSigner { + return result, fmt.Errorf("signing_key values 2-6 are reserved. Got %v", result.SigningKey) + } + result.MaskChipKey = (signerInfo & 2) != 0 + result.AuthorKeyEn = (signerInfo & 1) != 0 + return result, nil +} + +// ComposeSignerInfo returns the uint32 value expected to populate the attestation report byte range +// 0x48:0x4C. +func ComposeSignerInfo(signerInfo SignerInfo) uint32 { + var result uint32 + if signerInfo.AuthorKeyEn { + result |= 1 + } + if signerInfo.MaskChipKey { + result |= 2 + } + result |= uint32(signerInfo.SigningKey) << 2 + return result +} + +// ReportSignerInfo returns the signer info component of a SEV-SNP raw report. +func ReportSignerInfo(data []byte) (uint32, error) { + if len(data) < 0x4C { + return 0, fmt.Errorf("report too small: %d", len(data)) + } + return binary.LittleEndian.Uint32(data[0x48:0x4C]), nil +} + // ReportToProto creates a pb.Report from the little-endian AMD SEV-SNP attestation report byte // array in SEV SNP ABI format for ATTESTATION_REPORT. func ReportToProto(data []uint8) (*pb.Report, error) { @@ -357,11 +447,11 @@ func ReportToProto(data []uint8) (*pb.Report, error) { r.CurrentTcb = binary.LittleEndian.Uint64(data[0x38:0x40]) r.PlatformInfo = binary.LittleEndian.Uint64(data[0x40:0x48]) - reservedAuthor := binary.LittleEndian.Uint32(data[0x48:0x4C]) - if reservedAuthor&0xffffffe0 != 0 { - return nil, fmt.Errorf("mbz bits at offset 0x48 not zero: 0x%08x", reservedAuthor&0xffffffe0) + signerInfo, err := ParseSignerInfo(binary.LittleEndian.Uint32(data[0x48:0x4C])) + if err != nil { + return nil, err } - r.AuthorKeyEn = reservedAuthor + r.SignerInfo = ComposeSignerInfo(signerInfo) if err := mbz(data, 0x4C, 0x50); err != nil { return nil, err } @@ -480,11 +570,10 @@ func ReportToAbiBytes(r *pb.Report) ([]byte, error) { binary.LittleEndian.PutUint64(data[0x38:0x40], r.CurrentTcb) binary.LittleEndian.PutUint64(data[0x40:0x48], r.PlatformInfo) - var reservedAuthor uint32 - if r.AuthorKeyEn == 1 { - reservedAuthor |= 0x01 + if _, err := ParseSignerInfo(r.SignerInfo); err != nil { + return nil, err } - binary.LittleEndian.PutUint32(data[0x48:0x4C], reservedAuthor) + binary.LittleEndian.PutUint32(data[0x48:0x4C], r.SignerInfo) copy(data[0x50:0x90], r.ReportData[:]) copy(data[0x90:0xC0], r.Measurement[:]) copy(data[0xC0:0xE0], r.HostData[:]) @@ -680,12 +769,17 @@ func (c *CertTable) GetByGUIDString(guid string) ([]byte, error) { // so missing certificates aren't an error. If certificates are missing, you can // choose to fetch them yourself by calling verify.GetAttestationFromReport. func (c *CertTable) Proto() *pb.CertificateChain { - var vcek, ask, ark []byte - var err error - vcek, err = c.GetByGUIDString(VcekGUID) - if err != nil { - logger.Warningf("Warning: VCEK certificate not found in data pages: %v", err) + var vcek, vlek, ask, ark []byte + var err, cerr, lerr error + // Whereas a host is permitted to populate its certificate chain blob with both a VCEK and VLEK + // certificate, doing so is unusual since the choice of VCEK vs VLEK is an infrastructural choice. + // To keep the implementation clean, we don't pun vcek and vlek in the same field. + vcek, cerr = c.GetByGUIDString(VcekGUID) + vlek, lerr = c.GetByGUIDString(VlekGUID) + if cerr != nil && lerr != nil { + logger.Warning("Warning: Neither VCEK nor VLEK certificate found in data pages") } + ask, err = c.GetByGUIDString(AskGUID) if err != nil { logger.Warningf("ASK certificate not found in data pages: %v", err) @@ -697,6 +791,7 @@ func (c *CertTable) Proto() *pb.CertificateChain { firmware, _ := c.GetByGUIDString(gce.FirmwareCertGUID) return &pb.CertificateChain{ VcekCert: vcek, + VlekCert: vlek, AskCert: ask, ArkCert: ark, FirmwareCert: firmware, diff --git a/abi/abi_test.go b/abi/abi_test.go index 607bfcb..aecdb64 100644 --- a/abi/abi_test.go +++ b/abi/abi_test.go @@ -40,6 +40,44 @@ var emptyReport = ` signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` +func TestMbz64(t *testing.T) { + tests := []struct { + data uint64 + lo int + hi int + wantErr string + }{ + { + data: uint64(0), + lo: 0, + hi: 63, + }, + { + data: ^uint64(0) &^ (uint64(1<<31) | uint64(1<<32) | uint64(1<<33)), + lo: 31, + hi: 33, + }, + { + data: ^uint64(0) &^ (uint64(1<<0x1f) | uint64(1<<0x20)), + lo: 0x1f, + hi: 0x21, + wantErr: "mbz range test[0x1f:0x21] not all zero", + }, + { + data: ^uint64(0) &^ (uint64(1<<0x20) | uint64(1<<0x21)), + lo: 0x1f, + hi: 0x21, + wantErr: "mbz range test[0x1f:0x21] not all zero", + }, + } + for _, tc := range tests { + err := mbz64(tc.data, "test", tc.hi, tc.lo) + if (tc.wantErr == "" && err != nil) || (tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr))) { + t.Errorf("mbz64(0x%x, %d, %d) = %v, want %q", tc.data, tc.hi, tc.lo, err, tc.wantErr) + } + } +} + func TestReportMbz(t *testing.T) { tests := []struct { name string @@ -50,7 +88,7 @@ func TestReportMbz(t *testing.T) { { name: "AuthorKeyEn reserved", changeIndex: 0x49, - wantErr: "mbz bits at offset 0x48 not zero: 0x0000cc00", + wantErr: "mbz range data[0x48:0x4C][0x5:0x1f] not all zero: cc00", }, { name: "pre-report data", @@ -92,7 +130,7 @@ func TestReportMbz(t *testing.T) { name: "Guest policy bit 21", changeIndex: policyOffset + 2, // Bits 16-23 changeValue: 0x22, // Set bits 17, 21 - wantErr: "policy[63:21] are reserved mbz, got 0x220000", + wantErr: "malformed guest policy: mbz range policy[0x15:0x3f] not all zero: 220000", }, } reportProto := &spb.Report{} diff --git a/client/client_test.go b/client/client_test.go index 4aec4e7..3e45ba5 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -177,7 +177,13 @@ func TestOpenGetRawExtendedReportClose(t *testing.T) { } if UseDefaultSevGuest() { tcdev := device.(*test.Device) - if err := tcdev.Signer.Vcek.CheckSignature(x509.ECDSAWithSHA384, got, der); err != nil { + infoRaw, _ := abi.ReportSignerInfo(raw) + info, _ := abi.ParseSignerInfo(infoRaw) + reportSigner := tcdev.Signer.Vcek + if info.SigningKey == abi.VlekReportSigner { + reportSigner = tcdev.Signer.Vlek + } + if err := reportSigner.CheckSignature(x509.ECDSAWithSHA384, got, der); err != nil { t.Errorf("signature with test keys did not verify: %v", err) } } diff --git a/kds/kds.go b/kds/kds.go index 416e04e..2a646ff 100644 --- a/kds/kds.go +++ b/kds/kds.go @@ -31,120 +31,130 @@ import ( "go.uber.org/multierr" ) -// Encapsulates the rest of the fields after AMD's VCEK OID classifier prefix 1.3.6.1.4.1.3704.1. -type vcekOID struct { +// Encapsulates the rest of the fields after AMD's V{C,L}EK OID classifier prefix 1.3.6.1.4.1.3704.1. +type kdsOID struct { major int minor int } var ( - // OidStructVersion is the x509v3 extension for VCEK certificate struct version. + // OidStructVersion is the x509v3 extension for V[CL]EK certificate struct version. OidStructVersion = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 1}) - // OidProductName1 is the x509v3 extension for VCEK certificate product name. + // OidProductName1 is the x509v3 extension for V[CL]EK certificate product name. OidProductName1 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 2}) - // OidBlSpl is the x509v3 extension for VCEK certificate bootloader security patch level. + // OidBlSpl is the x509v3 extension for V[CL]EK certificate bootloader security patch level. OidBlSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 1}) - // OidTeeSpl is the x509v3 extension for VCEK certificate TEE security patch level. + // OidTeeSpl is the x509v3 extension for V[CL]EK certificate TEE security patch level. OidTeeSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 2}) - // OidSnpSpl is the x509v3 extension for VCEK certificate SNP security patch level. + // OidSnpSpl is the x509v3 extension for V[CL]EK certificate SNP security patch level. OidSnpSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 3}) - // OidSpl4 is the x509v3 extension for VCEK certificate reserved security patch level. + // OidSpl4 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl4 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 4}) - // OidSpl5 is the x509v3 extension for VCEK certificate reserved security patch level. + // OidSpl5 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl5 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 5}) - // OidSpl6 is the x509v3 extension for VCEK certificate reserved security patch level. + // OidSpl6 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl6 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 6}) - // OidSpl7 is the x509v3 extension for VCEK certificate reserved security patch level. + // OidSpl7 is the x509v3 extension for V[CL]EK certificate reserved security patch level. OidSpl7 = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 7}) - // OidUcodeSpl is the x509v3 extension for VCEK microcode security patch level. + // OidUcodeSpl is the x509v3 extension for V[CL]EK microcode security patch level. OidUcodeSpl = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 3, 8}) // OidHwid is the x509v3 extension for VCEK certificate associated hardware identifier. OidHwid = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 4}) + // OidCspID is the x509v3 extension for a VLEK certificate's Cloud Service Provider's + // origin TLS key's certificate's subject key's CommonName. + OidCspID = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 3704, 1, 5}) authorityKeyOid = asn1.ObjectIdentifier([]int{2, 5, 29, 35}) // Short forms of the asn1 Object identifiers to use in map lookups, since []int are invalid key // types. - vcekStructVersion = vcekOID{major: 1} - vcekProductName1 = vcekOID{major: 2} - vcekBlSpl = vcekOID{major: 3, minor: 1} - vcekTeeSpl = vcekOID{major: 3, minor: 2} - vcekSnpSpl = vcekOID{major: 3, minor: 3} - vcekSpl4 = vcekOID{major: 3, minor: 4} - vcekSpl5 = vcekOID{major: 3, minor: 5} - vcekSpl6 = vcekOID{major: 3, minor: 6} - vcekSpl7 = vcekOID{major: 3, minor: 7} - vcekUcodeSpl = vcekOID{major: 3, minor: 8} - vcekHwid = vcekOID{major: 4} + kdsStructVersion = kdsOID{major: 1} + kdsProductName1 = kdsOID{major: 2} + kdsBlSpl = kdsOID{major: 3, minor: 1} + kdsTeeSpl = kdsOID{major: 3, minor: 2} + kdsSnpSpl = kdsOID{major: 3, minor: 3} + kdsSpl4 = kdsOID{major: 3, minor: 4} + kdsSpl5 = kdsOID{major: 3, minor: 5} + kdsSpl6 = kdsOID{major: 3, minor: 6} + kdsSpl7 = kdsOID{major: 3, minor: 7} + kdsUcodeSpl = kdsOID{major: 3, minor: 8} + kdsHwid = kdsOID{major: 4} + kdsCspID = kdsOID{major: 5} kdsHostname = "kdsintf.amd.com" kdsBaseURL = "https://" + kdsHostname kdsVcekPath = "/vcek/v1/" + kdsVlekPath = "/vlek/v1/" ) // TCBVersion is a 64-bit bitfield of different security patch levels of AMD firmware and microcode. type TCBVersion uint64 -// VcekExtensions represents the information stored in the KDS-specified x509 extensions of a VCEK +// Extensions represents the information stored in the KDS-specified x509 extensions of a V{C,L}EK // certificate. -type VcekExtensions struct { +type Extensions struct { StructVersion uint8 ProductName string // The host driver knows the difference between primary and secondary HWID. - // Primary vs secondary is irrelevant to verification. - HWID [64]byte + // Primary vs secondary is irrelevant to verification. Must be nil or + // abi.ChipIDSize long. + HWID []byte TCBVersion TCBVersion + CspID string } -func oidTovcekOID(id asn1.ObjectIdentifier) (vcekOID, error) { +func oidTokdsOID(id asn1.ObjectIdentifier) (kdsOID, error) { if id.Equal(OidStructVersion) { - return vcekStructVersion, nil + return kdsStructVersion, nil } if id.Equal(OidProductName1) { - return vcekProductName1, nil + return kdsProductName1, nil } if id.Equal(OidBlSpl) { - return vcekBlSpl, nil + return kdsBlSpl, nil } if id.Equal(OidHwid) { - return vcekHwid, nil + return kdsHwid, nil } if id.Equal(OidTeeSpl) { - return vcekTeeSpl, nil + return kdsTeeSpl, nil } if id.Equal(OidSnpSpl) { - return vcekSnpSpl, nil + return kdsSnpSpl, nil } if id.Equal(OidSpl4) { - return vcekSpl4, nil + return kdsSpl4, nil } if id.Equal(OidSpl5) { - return vcekSpl5, nil + return kdsSpl5, nil } if id.Equal(OidSpl6) { - return vcekSpl6, nil + return kdsSpl6, nil } if id.Equal(OidSpl7) { - return vcekSpl7, nil + return kdsSpl7, nil } if id.Equal(OidUcodeSpl) { - return vcekUcodeSpl, nil + return kdsUcodeSpl, nil } - return vcekOID{}, fmt.Errorf("not an AMD VCEK OID: %v", id) + if id.Equal(OidCspID) { + return kdsCspID, nil + } + return kdsOID{}, fmt.Errorf("not an AMD KDS OID: %v", id) } -func vcekOidMap(cert *x509.Certificate) (map[vcekOID]*pkix.Extension, error) { - result := make(map[vcekOID]*pkix.Extension) +func kdsOidMap(cert *x509.Certificate) (map[kdsOID]*pkix.Extension, error) { + result := make(map[kdsOID]*pkix.Extension) for i, ext := range cert.Extensions { if ext.Id.Equal(authorityKeyOid) { // Since ASK is a CA, signing can impart the authority key extension. continue } - oid, err := oidTovcekOID(ext.Id) + oid, err := oidTokdsOID(ext.Id) if err != nil { return nil, err } if _, ok := result[oid]; ok { - return nil, fmt.Errorf("duplicate VCEK extension: %v", ext) + return nil, fmt.Errorf("duplicate AMD KDS extension: %v", ext) } result[oid] = &cert.Extensions[i] } @@ -293,43 +303,55 @@ func asn1OctetString(ext *pkix.Extension, field string, size int) ([]byte, error return octet, nil } -func vcekOidMapToVcekExtensions(exts map[vcekOID]*pkix.Extension) (*VcekExtensions, error) { - var result VcekExtensions +func kdsOidMapToExtensions(exts map[kdsOID]*pkix.Extension) (*Extensions, error) { + var result Extensions - if err := asn1U8(exts[vcekStructVersion], "StructVersion", &result.StructVersion); err != nil { + if err := asn1U8(exts[kdsStructVersion], "StructVersion", &result.StructVersion); err != nil { return nil, err } - if err := asn1IA5String(exts[vcekProductName1], "ProductName1", &result.ProductName); err != nil { + if err := asn1IA5String(exts[kdsProductName1], "ProductName1", &result.ProductName); err != nil { return nil, err } - octet, err := asn1OctetString(exts[vcekHwid], "HWID", 64) - if err != nil { - return nil, err + hwidExt, ok := exts[kdsHwid] + if ok { + octet, err := asn1OctetString(hwidExt, "HWID", 64) + if err != nil { + return nil, err + } + result.HWID = octet + } + cspidExt := exts[kdsCspID] + if cspidExt != nil { + if err := asn1IA5String(cspidExt, "CSP_ID", &result.CspID); err != nil { + return nil, err + } + if hwidExt != nil { + return nil, fmt.Errorf("certificate has both HWID (%s) and CSP_ID (%s) extensions", hex.EncodeToString(result.HWID), result.CspID) + } } - copy(result.HWID[:], octet) var blspl, snpspl, teespl, spl4, spl5, spl6, spl7, ucodespl uint8 - if err := asn1U8(exts[vcekBlSpl], "BlSpl", &blspl); err != nil { + if err := asn1U8(exts[kdsBlSpl], "BlSpl", &blspl); err != nil { return nil, err } - if err := asn1U8(exts[vcekTeeSpl], "TeeSpl", &teespl); err != nil { + if err := asn1U8(exts[kdsTeeSpl], "TeeSpl", &teespl); err != nil { return nil, err } - if err := asn1U8(exts[vcekSnpSpl], "SnpSpl", &snpspl); err != nil { + if err := asn1U8(exts[kdsSnpSpl], "SnpSpl", &snpspl); err != nil { return nil, err } - if err := asn1U8(exts[vcekSpl4], "Spl4", &spl4); err != nil { + if err := asn1U8(exts[kdsSpl4], "Spl4", &spl4); err != nil { return nil, err } - if err := asn1U8(exts[vcekSpl5], "Spl5", &spl5); err != nil { + if err := asn1U8(exts[kdsSpl5], "Spl5", &spl5); err != nil { return nil, err } - if err := asn1U8(exts[vcekSpl6], "Spl6", &spl6); err != nil { + if err := asn1U8(exts[kdsSpl6], "Spl6", &spl6); err != nil { return nil, err } - if err := asn1U8(exts[vcekSpl7], "Spl7", &spl7); err != nil { + if err := asn1U8(exts[kdsSpl7], "Spl7", &spl7); err != nil { return nil, err } - if err := asn1U8(exts[vcekUcodeSpl], "UcodeSpl", &ucodespl); err != nil { + if err := asn1U8(exts[kdsUcodeSpl], "UcodeSpl", &ucodespl); err != nil { return nil, err } tcb, err := ComposeTCBParts(TCBParts{ @@ -349,20 +371,66 @@ func vcekOidMapToVcekExtensions(exts map[vcekOID]*pkix.Extension) (*VcekExtensio return &result, nil } -// VcekCertificateExtensions returns the x509v3 extensions from the KDS specification interpreted -// into a struct type. -func VcekCertificateExtensions(cert *x509.Certificate) (*VcekExtensions, error) { - oidMap, err := vcekOidMap(cert) +// preEndorsementKeyCertificateExtensions returns the x509v3 extensions from the KDS specification interpreted +// into a struct type for either the VCEK or the VLEK +func preEndorsementKeyCertificateExtensions(cert *x509.Certificate) (*Extensions, error) { + oidMap, err := kdsOidMap(cert) if err != nil { return nil, err } - extensions, err := vcekOidMapToVcekExtensions(oidMap) + extensions, err := kdsOidMapToExtensions(oidMap) if err != nil { return nil, err } return extensions, nil } +// VcekCertificateExtensions returns the x509v3 extensions from the KDS specification of a VCEK +// certificate interpreted into a struct type. +func VcekCertificateExtensions(cert *x509.Certificate) (*Extensions, error) { + exts, err := preEndorsementKeyCertificateExtensions(cert) + if err != nil { + return nil, err + } + if exts.CspID != "" { + return nil, fmt.Errorf("unexpected CSP_ID in VCEK certificate: %s", exts.CspID) + } + if len(exts.HWID) != abi.ChipIDSize { + return nil, fmt.Errorf("missing HWID extension for VCEK certificate") + } + return exts, nil +} + +// VlekCertificateExtensions returns the x509v3 extensions from the KDS specification of a VLEK +// certificate interpreted into a struct type. +func VlekCertificateExtensions(cert *x509.Certificate) (*Extensions, error) { + exts, err := preEndorsementKeyCertificateExtensions(cert) + if err != nil { + return nil, err + } + if exts.CspID == "" { + return nil, fmt.Errorf("missing CSP_ID in VLEK certificate") + } + if exts.HWID != nil { + return nil, fmt.Errorf("unexpected HWID in VLEK certificate: %s", hex.EncodeToString(exts.HWID)) + } + return exts, nil +} + +// CertificateExtensions returns the x509v3 extensions from the KDS specification interpreted +// into a struct type. +func CertificateExtensions(cert *x509.Certificate, key abi.ReportSigner) (*Extensions, error) { + switch key { + case abi.VcekReportSigner: + return VcekCertificateExtensions(cert) + case abi.VlekReportSigner: + return VlekCertificateExtensions(cert) + case abi.NoneReportSigner: + return &Extensions{}, nil + } + return nil, fmt.Errorf("unexpected endorsement key kind %v", key) +} + // ParseProductCertChain returns the DER-formatted certificates represented by the body // of the ProductCertChain (cert_chain) endpoint, ASK and ARK in that order. func ParseProductCertChain(pems []byte) ([]byte, []byte, error) { @@ -380,7 +448,7 @@ func ParseProductCertChain(pems []byte) ([]byte, []byte, error) { } askBlock, arkRest := pem.Decode(pems) arkBlock, noRest := pem.Decode(arkRest) - if err := multierr.Combine(checkForm("ASK", askBlock), checkForm("ARK", arkBlock)); err != nil { + if err := multierr.Combine(checkForm("ASK or ASVK", askBlock), checkForm("ARK", arkBlock)); err != nil { return nil, nil, err } if len(noRest) != 0 { @@ -389,15 +457,23 @@ func ParseProductCertChain(pems []byte) ([]byte, []byte, error) { return askBlock.Bytes, arkBlock.Bytes, nil } -// productBaseURL returns the base URL for all certificate queries within a particular product. -func productBaseURL(name string) string { - return fmt.Sprintf("%s/vcek/v1/%s", kdsBaseURL, name) +// productBaseURL returns the base URL for all certificate queries within a particular product for the +// given report signer kind. +func productBaseURL(s abi.ReportSigner, name string) string { + path := "unknown" + if s == abi.VcekReportSigner { + path = kdsVcekPath + } + if s == abi.VlekReportSigner { + path = kdsVlekPath + } + return fmt.Sprintf("%s%s%s", kdsBaseURL, path, name) } -// ProductCertChainURL returns the AMD KDS URL for retrieving the ARK and ASK -// certificates on the given product in PEM format. -func ProductCertChainURL(product string) string { - return fmt.Sprintf("%s/cert_chain", productBaseURL(product)) +// ProductCertChainURL returns the AMD KDS URL for retrieving the ARK and AS(V)K +// certificates on the given product in ??? format. +func ProductCertChainURL(s abi.ReportSigner, product string) string { + return fmt.Sprintf("%s/cert_chain", productBaseURL(s, product)) } // VCEKCertURL returns the AMD KDS URL for retrieving the VCEK on a given product @@ -405,7 +481,7 @@ func ProductCertChainURL(product string) string { func VCEKCertURL(product string, hwid []byte, tcb TCBVersion) string { parts := DecomposeTCBVersion(tcb) return fmt.Sprintf("%s/%s?blSPL=%d&teeSPL=%d&snpSPL=%d&ucodeSPL=%d", - productBaseURL(product), + productBaseURL(abi.VcekReportSigner, product), hex.EncodeToString(hwid), parts.BlSpl, parts.TeeSpl, @@ -414,6 +490,19 @@ func VCEKCertURL(product string, hwid []byte, tcb TCBVersion) string { ) } +// VLEKCertURL returns the GET URL for retrieving a VLEK certificate, but without the necessary +// CSP secret in the HTTP headers that makes the request validate to the KDS. +func VLEKCertURL(product string, tcb TCBVersion) string { + parts := DecomposeTCBVersion(tcb) + return fmt.Sprintf("%s/cert?blSPL=%d&teeSPL=%d&snpSPL=%d&ucodeSPL=%d", + productBaseURL(abi.VlekReportSigner, product), + parts.BlSpl, + parts.TeeSpl, + parts.SnpSpl, + parts.UcodeSpl, + ) +} + // VCEKCert represents the attestation report components represented in a KDS VCEK certificate // request URL. type VCEKCert struct { @@ -422,71 +511,88 @@ type VCEKCert struct { TCB uint64 } +// VLEKCert represents the attestation report components represented in a KDS VLEK certificate +// request URL. +type VLEKCert struct { + Product string + TCB uint64 +} + +// CertFunction is an enumeration of which endorsement key type is getting certified. +type CertFunction int + +const ( + // UnknownCertFunction represents an unknown endpoint for parsing KDS URLs. + UnknownCertFunction CertFunction = iota + // VcekCertFunction represents the vcek endpoints for parsing KDS URLs. + VcekCertFunction + // VlekCertFunction represents the vlek endpoints for parsing KDS URLs. + VlekCertFunction +) + +type parsedURL struct { + product string + simpleURL *url.URL + function CertFunction +} + // parseBaseProductURL returns the product name for a root certificate chain URL if it is one, // with the parsed URL that has the product prefix trimmed. -func parseBaseProductURL(kdsurl string) (string, *url.URL, error) { +func parseBaseProductURL(kdsurl string) (*parsedURL, error) { u, err := url.Parse(kdsurl) if err != nil { - return "", nil, fmt.Errorf("invalid AMD KDS URL %q: %v", kdsurl, err) + return nil, fmt.Errorf("invalid AMD KDS URL %q: %v", kdsurl, err) } if u.Scheme != "https" { - return "", nil, fmt.Errorf("unexpected AMD KDS URL scheme %q, want \"https\"", u.Scheme) + return nil, fmt.Errorf("unexpected AMD KDS URL scheme %q, want \"https\"", u.Scheme) } if u.Host != kdsHostname { - return "", nil, fmt.Errorf("unexpected AMD KDS URL host %q, want %q", u.Host, kdsHostname) + return nil, fmt.Errorf("unexpected AMD KDS URL host %q, want %q", u.Host, kdsHostname) + } + result := &parsedURL{} + vcekFunc := strings.HasPrefix(u.Path, kdsVcekPath) + vlekFunc := strings.HasPrefix(u.Path, kdsVlekPath) + var function string + if vcekFunc { + function = strings.TrimPrefix(u.Path, kdsVcekPath) + result.function = VcekCertFunction + } else if vlekFunc { + function = strings.TrimPrefix(u.Path, kdsVlekPath) + result.function = VlekCertFunction + } else { + return nil, fmt.Errorf("unexpected AMD KDS URL path %q, want prefix %q or %q", u.Path, kdsVcekPath, kdsVlekPath) } - if !strings.HasPrefix(u.Path, kdsVcekPath) { - return "", nil, fmt.Errorf("unexpected AMD KDS URL path %q, want prefix %q", u.Path, kdsVcekPath) - } - function := strings.TrimPrefix(u.Path, kdsVcekPath) // The following should be product/endpoint pieces := strings.Split(function, "/") if len(pieces) != 2 { - return "", nil, fmt.Errorf("url has unexpected endpoint %q not product/endpoint", function) + return nil, fmt.Errorf("url has unexpected endpoint %q not product/endpoint", function) } - product := pieces[0] + result.product = pieces[0] // Set the URL's path to the rest of the path without the API or product prefix. u.Path = pieces[1] - return product, u, nil + result.simpleURL = u + return result, nil } -// ParseProductCertChainURL returns the product name for a KDS cert_chain url, or an error if the -// input is not a KDS cert_chain url. -func ParseProductCertChainURL(kdsurl string) (string, error) { - product, u, err := parseBaseProductURL(kdsurl) +// ParseProductCertChainURL returns the product name and either "vcek" or "vlek" for a KDS +// cert_chain url, or an error if the input is not a KDS cert_chain url. +func ParseProductCertChainURL(kdsurl string) (string, CertFunction, error) { + parsed, err := parseBaseProductURL(kdsurl) if err != nil { - return "", err + return "", UnknownCertFunction, err } - if u.Path != "cert_chain" { - return "", fmt.Errorf("unexpected AMD KDS URL path %q, want \"cert_chain\"", u.Path) + if parsed.simpleURL.Path != "cert_chain" { + return "", UnknownCertFunction, fmt.Errorf("unexpected AMD KDS URL path %q, want \"cert_chain\"", parsed.simpleURL.Path) } - return product, nil + return parsed.product, parsed.function, nil } -// ParseVCEKCertURL returns the attestation report components represented in the given KDS VCEK -// certificate request URL. -func ParseVCEKCertURL(kdsurl string) (VCEKCert, error) { - result := VCEKCert{} - product, u, err := parseBaseProductURL(kdsurl) - if err != nil { - return result, err - } - result.Product = product - hwid, err := hex.DecodeString(u.Path) - if err != nil { - return result, fmt.Errorf("hwid component of KDS URL is not a hex string: %q", u.Path) - } - if len(hwid) != abi.ChipIDSize { - return result, fmt.Errorf("hwid component of KDS URL has size %d, want %d", len(hwid), abi.ChipIDSize) - } - - result.HWID = hwid - +func parseTCBURL(u *url.URL) (uint64, error) { values, err := url.ParseQuery(u.RawQuery) if err != nil { - return result, fmt.Errorf("invalid AMD KDS URL query %q: %v", u.RawQuery, err) + return 0, fmt.Errorf("invalid AMD KDS URL query %q: %v", u.RawQuery, err) } parts := TCBParts{} for key, valuelist := range values { @@ -501,22 +607,67 @@ func ParseVCEKCertURL(kdsurl string) (VCEKCert, error) { case "ucodeSPL": setter = func(number uint8) { parts.UcodeSpl = number } default: - return result, fmt.Errorf("unexpected KDS VCEK URL argument %q", key) + return 0, fmt.Errorf("unexpected KDS TCB version URL argument %q", key) } for _, val := range valuelist { number, err := strconv.Atoi(val) if err != nil || number < 0 || number > 255 { - return result, fmt.Errorf("invalid KDS VCEK URL argument value %q, want a value 0-255", val) + return 0, fmt.Errorf("invalid KDS TCB version URL argument value %q, want a value 0-255", val) } setter(uint8(number)) } } tcb, err := ComposeTCBParts(parts) if err != nil { - return result, fmt.Errorf("invalid AMD KDS TCB arguments: %v", err) + return 0, fmt.Errorf("invalid AMD KDS TCB arguments: %v", err) } - result.TCB = uint64(tcb) - return result, nil + return uint64(tcb), err +} + +// ParseVCEKCertURL returns the attestation report components represented in the given KDS VCEK +// certificate request URL. +func ParseVCEKCertURL(kdsurl string) (VCEKCert, error) { + result := VCEKCert{} + parsed, err := parseBaseProductURL(kdsurl) + if err != nil { + return result, err + } + if parsed.function != VcekCertFunction { + return result, fmt.Errorf("not a VCEK certificate URL: %s", kdsurl) + } + result.Product = parsed.product + hwid, err := hex.DecodeString(parsed.simpleURL.Path) + if err != nil { + return result, fmt.Errorf("hwid component of KDS URL is not a hex string: %q", parsed.simpleURL.Path) + } + if len(hwid) != abi.ChipIDSize { + return result, fmt.Errorf("hwid component of KDS URL has size %d, want %d", len(hwid), abi.ChipIDSize) + } + + result.HWID = hwid + + result.TCB, err = parseTCBURL(parsed.simpleURL) + return result, err +} + +// ParseVLEKCertURL returns the attestation report components represented in the given KDS VLEK +// certificate request URL. +func ParseVLEKCertURL(kdsurl string) (VLEKCert, error) { + result := VLEKCert{} + parsed, err := parseBaseProductURL(kdsurl) + if err != nil { + return result, err + } + if parsed.function != VlekCertFunction { + return result, fmt.Errorf("not a VLEK certificate URL: %s", kdsurl) + } + result.Product = parsed.product + if parsed.simpleURL.Path != "cert" { + return result, fmt.Errorf("vlek function is %q, want 'cert'", parsed.simpleURL.Path) + } + + result.TCB, err = parseTCBURL(parsed.simpleURL) + return result, err } // ProductString returns the KDS product argument to use for the product associated with @@ -545,24 +696,57 @@ func ProductName(product *pb.SevProduct) string { } // ParseProductName returns the KDS project input value, and the model, stepping numbers represented -// by a given VCEK productName extension value, or an error. -func ParseProductName(productName string) (*pb.SevProduct, error) { - subs := strings.SplitN(productName, "-", 2) - if len(subs) != 2 { - return nil, fmt.Errorf("productName value %q does not match the expected Name-ModelStepping format", productName) +// by a given V[CL]EK productName extension value, or an error. +func ParseProductName(productName string, key abi.ReportSigner) (*pb.SevProduct, error) { + var product, stepping string + var needStepping bool + switch key { + case abi.VcekReportSigner: + subs := strings.SplitN(productName, "-", 2) + if len(subs) != 2 { + return nil, fmt.Errorf("productName value %q does not match the VCEK expected Name-ModelStepping format", productName) + } + product = subs[0] + stepping = subs[1] + needStepping = true + case abi.VlekReportSigner: + // VLEK certificates don't carry the stepping value in productName. + product = productName } var name pb.SevProduct_SevProductName - switch subs[0] { + switch product { case "Milan": name = pb.SevProduct_SEV_PRODUCT_MILAN case "Genoa": name = pb.SevProduct_SEV_PRODUCT_GENOA default: - return nil, fmt.Errorf("unknown AMD SEV product: %q", subs[0]) + return nil, fmt.Errorf("unknown AMD SEV product: %q", product) } - modelStepping, err := strconv.ParseUint(subs[1], 16, 8) - if err != nil { - return nil, fmt.Errorf("model stepping in productName is not a hexadecimal byte: %q", subs[1]) + var modelStepping uint64 + if needStepping { + var err error + modelStepping, err = strconv.ParseUint(stepping, 16, 8) + if err != nil { + return nil, fmt.Errorf("model stepping in productName is not a hexadecimal byte: %q", stepping) + } } return &pb.SevProduct{Name: name, ModelStepping: uint32(modelStepping)}, nil } + +// CrlLinkByKey returns the CRL distribution point for the given key type's +// product. If key is VlekReportSigner, then we use the vlek endpoint. The ASK +// and ARK are both on the vcek endpoint. +func CrlLinkByKey(product string, key abi.ReportSigner) string { + return fmt.Sprintf("%s/crl", productBaseURL(key, product)) +} + +// CrlLinkByRole returns the CRL distribution point for the given key role's +// product. If role is "ASVK", then we use the vlek endpoint. The ASK and ARK +// are both on the vcek endpoint. +func CrlLinkByRole(product, role string) string { + key := abi.VcekReportSigner + if role == "ASVK" { + key = abi.VlekReportSigner + } + return CrlLinkByKey(product, key) +} diff --git a/kds/kds_test.go b/kds/kds_test.go index 194e131..dd27e50 100644 --- a/kds/kds_test.go +++ b/kds/kds_test.go @@ -28,7 +28,7 @@ import ( ) func TestProductCertChainURL(t *testing.T) { - got := ProductCertChainURL("Milan") + got := ProductCertChainURL(abi.VcekReportSigner, "Milan") want := "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain" if got != want { t.Errorf("ProductCertChainURL(\"Milan\") = %q, want %q", got, want) @@ -56,7 +56,7 @@ func TestParseProductBaseURL(t *testing.T) { }{ { name: "happy path", - url: ProductCertChainURL("Milan"), + url: ProductCertChainURL(abi.VcekReportSigner, "Milan"), wantProduct: "Milan", wantURL: &url.URL{ Scheme: "https", @@ -82,16 +82,16 @@ func TestParseProductBaseURL(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - gotProduct, gotURL, err := parseBaseProductURL(tc.url) + parsed, err := parseBaseProductURL(tc.url) if (err == nil && tc.wantErr != "") || (err != nil && !strings.Contains(err.Error(), tc.wantErr)) { t.Fatalf("parseBaseProductURL(%q) = _, _, %v, want %q", tc.url, err, tc.wantErr) } if err == nil { - if diff := cmp.Diff(gotURL, tc.wantURL); diff != "" { + if diff := cmp.Diff(parsed.simpleURL, tc.wantURL); diff != "" { t.Errorf("parseBaseProductURL(%q) returned unexpected diff (-want +got):\n%s", tc.url, diff) } - if gotProduct != tc.wantProduct { - t.Errorf("parseBaseProductURL(%q) = %q, _, _ want %q", tc.url, gotProduct, tc.wantProduct) + if parsed.product != tc.wantProduct { + t.Errorf("parseBaseProductURL(%q) = %q, _, _ want %q", tc.url, parsed.product, tc.wantProduct) } } }) @@ -99,13 +99,31 @@ func TestParseProductBaseURL(t *testing.T) { } func TestParseProductCertChainURL(t *testing.T) { - url := ProductCertChainURL("Milan") - got, err := ParseProductCertChainURL(url) - if err != nil { - t.Fatalf("ParseProductCertChainURL(%q) = _, %v, want nil", "Milan", err) + tests := []struct { + key abi.ReportSigner + product string + wantKey CertFunction + }{ + { + key: abi.VcekReportSigner, + product: "Milan", + wantKey: VcekCertFunction, + }, + { + key: abi.VlekReportSigner, + product: "Milan", + wantKey: VlekCertFunction, + }, } - if got != "Milan" { - t.Errorf("ProductCertChainURL(%q) = %q, nil want %q", url, got, "Milan") + for _, tc := range tests { + url := ProductCertChainURL(tc.key, tc.product) + got, key, err := ParseProductCertChainURL(url) + if err != nil { + t.Fatalf("ParseProductCertChainURL(%q) = _, _, %v, want nil", tc.product, err) + } + if got != tc.product || key != tc.wantKey { + t.Errorf("ProductCertChainURL(%q) = %q, %v, nil want %q, %v", url, got, key, tc.product, tc.wantKey) + } } } @@ -131,17 +149,17 @@ func TestParseVCEKCertURL(t *testing.T) { { name: "bad query key", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?fakespl=4", hwidhex), - wantErr: "unexpected KDS VCEK URL argument \"fakespl\"", + wantErr: "unexpected KDS TCB version URL argument \"fakespl\"", }, { name: "bad query argument numerical", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?blSPL=-4", hwidhex), - wantErr: "invalid KDS VCEK URL argument value \"-4\", want a value 0-255", + wantErr: "invalid KDS TCB version URL argument value \"-4\", want a value 0-255", }, { name: "bad query argument numerical", url: fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/Milan/%s?blSPL=alpha", hwidhex), - wantErr: "invalid KDS VCEK URL argument value \"alpha\", want a value 0-255", + wantErr: "invalid KDS TCB version URL argument value \"alpha\", want a value 0-255", }, } for _, tc := range tcs { @@ -205,6 +223,7 @@ func TestParseProductName(t *testing.T) { tcs := []struct { name string input string + key abi.ReportSigner want *pb.SevProduct wantErr string }{ @@ -240,10 +259,18 @@ func TestParseProductName(t *testing.T) { ModelStepping: 0x9C, }, }, + { + name: "vlek products have no stepping", + input: "Genoa", + key: abi.VlekReportSigner, + want: &pb.SevProduct{ + Name: pb.SevProduct_SEV_PRODUCT_GENOA, + }, + }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - got, err := ParseProductName(tc.input) + got, err := ParseProductName(tc.input, tc.key) if (err == nil && tc.wantErr != "") || (err != nil && (tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr))) { t.Fatalf("ParseProductName(%v) errored unexpectedly: %v, want %q", tc.input, err, tc.wantErr) } diff --git a/proto/check.proto b/proto/check.proto index 189c261..44e4696 100644 --- a/proto/check.proto +++ b/proto/check.proto @@ -59,7 +59,7 @@ message RootOfTrust { string product = 1; // Paths to CA bundles for the AMD product. - // Must be in PEM format, ASK, then ARK certificates. + // Must be in PEM format, AS[V]K, then ARK certificates. // This is for verifing a report's signature, as opposed to validating trust // in the report's ID key or author key. // If empty, uses the verification library's embedded certificates from AMD. diff --git a/proto/check/check.pb.go b/proto/check/check.pb.go index 425d0c3..b44e30a 100644 --- a/proto/check/check.pb.go +++ b/proto/check/check.pb.go @@ -275,7 +275,7 @@ type RootOfTrust struct { // The expected AMD product the attestation was collected from. Default "Milan". Product string `protobuf:"bytes,1,opt,name=product,proto3" json:"product,omitempty"` // Paths to CA bundles for the AMD product. - // Must be in PEM format, ASK, then ARK certificates. + // Must be in PEM format, AS[V]K, then ARK certificates. // This is for verifing a report's signature, as opposed to validating trust // in the report's ID key or author key. // If empty, uses the verification library's embedded certificates from AMD. diff --git a/proto/sevsnp.proto b/proto/sevsnp.proto index 5d7c5f1..2f77c6d 100644 --- a/proto/sevsnp.proto +++ b/proto/sevsnp.proto @@ -32,7 +32,7 @@ message Report { uint32 signature_algo = 7; uint64 current_tcb = 8; uint64 platform_info = 9; - uint32 author_key_en = 10; + uint32 signer_info = 10; // AuthorKeyEn, MaskChipKey, SigningKey bytes report_data = 11; // Should be 64 bytes long bytes measurement = 12; // Should be 48 bytes long bytes host_data = 13; // Should be 32 bytes long @@ -56,11 +56,15 @@ message Report { } message CertificateChain { - // The versioned chip endorsement key's certificate for the individual chip - // that signed this report. + // The versioned chip endorsement key's certificate for the + // key that signed this report. bytes vcek_cert = 1; - // The AMD SEV Signing key's certificate (signs the VCEK cert). + // The versioned loaded endorsement key's certificate for the + // key that signed this report. + bytes vlek_cert = 6; + + // The AMD SEV or AMD SEV-VLEK certificate that signed the V?EK cert. bytes ask_cert = 2; // The AMD Root key certificate (signs the ASK cert). diff --git a/proto/sevsnp/sevsnp.pb.go b/proto/sevsnp/sevsnp.pb.go index dfdf85f..4e9bc10 100644 --- a/proto/sevsnp/sevsnp.pb.go +++ b/proto/sevsnp/sevsnp.pb.go @@ -103,7 +103,7 @@ type Report struct { SignatureAlgo uint32 `protobuf:"varint,7,opt,name=signature_algo,json=signatureAlgo,proto3" json:"signature_algo,omitempty"` CurrentTcb uint64 `protobuf:"varint,8,opt,name=current_tcb,json=currentTcb,proto3" json:"current_tcb,omitempty"` PlatformInfo uint64 `protobuf:"varint,9,opt,name=platform_info,json=platformInfo,proto3" json:"platform_info,omitempty"` - AuthorKeyEn uint32 `protobuf:"varint,10,opt,name=author_key_en,json=authorKeyEn,proto3" json:"author_key_en,omitempty"` + SignerInfo uint32 `protobuf:"varint,10,opt,name=signer_info,json=signerInfo,proto3" json:"signer_info,omitempty"` // AuthorKeyEn, MaskChipKey, SigningKey ReportData []byte `protobuf:"bytes,11,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be 64 bytes long Measurement []byte `protobuf:"bytes,12,opt,name=measurement,proto3" json:"measurement,omitempty"` // Should be 48 bytes long HostData []byte `protobuf:"bytes,13,opt,name=host_data,json=hostData,proto3" json:"host_data,omitempty"` // Should be 32 bytes long @@ -221,9 +221,9 @@ func (x *Report) GetPlatformInfo() uint64 { return 0 } -func (x *Report) GetAuthorKeyEn() uint32 { +func (x *Report) GetSignerInfo() uint32 { if x != nil { - return x.AuthorKeyEn + return x.SignerInfo } return 0 } @@ -359,10 +359,13 @@ type CertificateChain struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The versioned chip endorsement key's certificate for the individual chip - // that signed this report. + // The versioned chip endorsement key's certificate for the + // key that signed this report. VcekCert []byte `protobuf:"bytes,1,opt,name=vcek_cert,json=vcekCert,proto3" json:"vcek_cert,omitempty"` - // The AMD SEV Signing key's certificate (signs the VCEK cert). + // The versioned loaded endorsement key's certificate for the + // key that signed this report. + VlekCert []byte `protobuf:"bytes,6,opt,name=vlek_cert,json=vlekCert,proto3" json:"vlek_cert,omitempty"` + // The AMD SEV or AMD SEV-VLEK certificate that signed the V?EK cert. AskCert []byte `protobuf:"bytes,2,opt,name=ask_cert,json=askCert,proto3" json:"ask_cert,omitempty"` // The AMD Root key certificate (signs the ASK cert). ArkCert []byte `protobuf:"bytes,3,opt,name=ark_cert,json=arkCert,proto3" json:"ark_cert,omitempty"` @@ -410,6 +413,13 @@ func (x *CertificateChain) GetVcekCert() []byte { return nil } +func (x *CertificateChain) GetVlekCert() []byte { + if x != nil { + return x.VlekCert + } + return nil +} + func (x *CertificateChain) GetAskCert() []byte { if x != nil { return x.AskCert @@ -557,7 +567,7 @@ var File_sevsnp_proto protoreflect.FileDescriptor var file_sevsnp_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, - 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x22, 0xab, 0x07, 0x0a, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x22, 0xa8, 0x07, 0x0a, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x76, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, @@ -574,84 +584,86 @@ var file_sevsnp_proto_rawDesc = []byte{ 0x63, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x63, 0x62, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x12, 0x1f, 0x0a, - 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x20, - 0x0a, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, - 0x0d, 0x69, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x64, 0x4b, 0x65, 0x79, 0x44, 0x69, 0x67, 0x65, 0x73, - 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, - 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x6d, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x4d, 0x61, 0x12, 0x21, 0x0a, 0x0c, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x12, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x54, 0x63, 0x62, 0x12, - 0x17, 0x0a, 0x07, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x06, 0x63, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x14, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x63, 0x62, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x15, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x0f, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, - 0x18, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, - 0x65, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x27, - 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, - 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, - 0x65, 0x64, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, - 0x68, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x61, 0x75, - 0x6e, 0x63, 0x68, 0x54, 0x63, 0x62, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x63, 0x65, - 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x76, 0x63, - 0x65, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x65, - 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x6b, 0x43, 0x65, 0x72, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x72, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x72, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, - 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x43, 0x65, 0x72, - 0x74, 0x22, 0xc3, 0x01, 0x0a, 0x0a, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, - 0x12, 0x35, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, - 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x74, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, - 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, - 0x5f, 0x73, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0d, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x57, - 0x0a, 0x0e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x17, 0x0a, 0x13, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, - 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x4d, 0x49, 0x4c, 0x41, 0x4e, 0x10, 0x01, - 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, - 0x47, 0x45, 0x4e, 0x4f, 0x41, 0x10, 0x02, 0x22, 0xaa, 0x01, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, - 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, - 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, - 0x45, 0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x65, 0x76, - 0x73, 0x6e, 0x70, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, - 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x74, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x65, 0x76, - 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x76, - 0x73, 0x6e, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x6d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, + 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x64, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x69, 0x64, 0x4b, 0x65, 0x79, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x2a, + 0x0a, 0x11, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x4b, 0x65, 0x79, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x6d, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x4d, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x12, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x54, 0x63, 0x62, 0x12, 0x17, 0x0a, 0x07, + 0x63, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, + 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, + 0x65, 0x64, 0x5f, 0x74, 0x63, 0x62, 0x18, 0x14, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x63, 0x62, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x18, 0x16, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4d, + 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, + 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x1a, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x4d, + 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x74, + 0x63, 0x62, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, + 0x54, 0x63, 0x62, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x63, 0x65, 0x6b, 0x5f, 0x63, + 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x76, 0x63, 0x65, 0x6b, 0x43, + 0x65, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x6c, 0x65, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x76, 0x6c, 0x65, 0x6b, 0x43, 0x65, 0x72, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x72, 0x6b, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, + 0x72, 0x6b, 0x43, 0x65, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, + 0x72, 0x65, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x43, 0x65, 0x72, 0x74, 0x22, 0xc3, 0x01, 0x0a, 0x0a, + 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, + 0x70, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x53, 0x65, 0x76, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x53, 0x74, 0x65, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x57, 0x0a, 0x0e, 0x53, 0x65, 0x76, 0x50, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x45, + 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, + 0x43, 0x54, 0x5f, 0x4d, 0x49, 0x4c, 0x41, 0x4e, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, + 0x56, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x47, 0x45, 0x4e, 0x4f, 0x41, 0x10, + 0x02, 0x22, 0xaa, 0x01, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x26, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x45, 0x0a, 0x11, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x10, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x2e, 0x53, 0x65, 0x76, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x2d, + 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x65, 0x76, 0x2d, 0x67, 0x75, 0x65, 0x73, 0x74, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x76, 0x73, 0x6e, 0x70, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/testing/client/client.go b/testing/client/client.go index f3f466a..db5b36e 100644 --- a/testing/client/client.go +++ b/testing/client/client.go @@ -19,6 +19,7 @@ import ( "fmt" "testing" + "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/client" test "github.com/google/go-sev-guest/testing" "github.com/google/go-sev-guest/verify/trust" @@ -47,8 +48,9 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) ( { Product: "Milan", ProductCerts: &trust.ProductCerts{ - Ask: sevTestDevice.Signer.Ask, - Ark: sevTestDevice.Signer.Ark, + Ask: sevTestDevice.Signer.Ask, + Ark: sevTestDevice.Signer.Ark, + Asvk: sevTestDevice.Signer.Asvk, }, }, }, @@ -59,8 +61,9 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) ( Product: "Milan", ProductCerts: &trust.ProductCerts{ // No ASK, oops. - Ask: sevTestDevice.Signer.Ark, - Ark: sevTestDevice.Signer.Ark, + Ask: sevTestDevice.Signer.Ark, + Ark: sevTestDevice.Signer.Ark, + Asvk: sevTestDevice.Signer.Ark, }, }, }, @@ -81,7 +84,7 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) ( badSnpRoot := make(map[string][]*trust.AMDRootCerts) for product, rootCerts := range trust.DefaultRootCerts { // Supplement the defaults with the missing x509 certificates. - pc, err := trust.GetProductChain(product, kdsImpl) + pc, err := trust.GetProductChain(product, abi.VcekReportSigner, kdsImpl) if err != nil { tb.Fatalf("failed to get product chain for %q: %v", product, err) } @@ -90,8 +93,9 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) ( badSnpRoot[product] = []*trust.AMDRootCerts{{ Product: product, ProductCerts: &trust.ProductCerts{ - Ark: pc.Ark, - Ask: pc.Ark, + Ark: pc.Ark, + Ask: pc.Ark, + Asvk: pc.Ark, }, AskSev: rootCerts.ArkSev, ArkSev: rootCerts.AskSev, diff --git a/testing/fake_certs.go b/testing/fake_certs.go index 2d87a6c..e78ef1b 100644 --- a/testing/fake_certs.go +++ b/testing/fake_certs.go @@ -41,6 +41,7 @@ import ( const ( arkExpirationYears = 25 askExpirationYears = 25 + asvkExpirationYears = 25 vcekExpirationYears = 7 arkRsaBits = 4096 askRsaBits = 4096 @@ -51,7 +52,9 @@ const ( type AmdSigner struct { Ark *x509.Certificate Ask *x509.Certificate + Asvk *x509.Certificate Vcek *x509.Certificate + Vlek *x509.Certificate Keys *AmdKeys // This identity does not match AMD's notion of an HWID. It is purely to combine expectations of // report data -> KDS URL construction for the fake KDS implementation. @@ -63,7 +66,9 @@ type AmdSigner struct { type AmdKeys struct { Ark *rsa.PrivateKey Ask *rsa.PrivateKey + Asvk *rsa.PrivateKey Vcek *ecdsa.PrivateKey + Vlek *ecdsa.PrivateKey } var insecureRandomness = rand.New(rand.NewSource(0xc0de)) @@ -71,9 +76,24 @@ var insecureRandomness = rand.New(rand.NewSource(0xc0de)) // Sign takes a chunk of bytes, signs it with VcekPriv, and returns the R, S pair for the signature // in little endian format. func (s *AmdSigner) Sign(toSign []byte) (*big.Int, *big.Int, error) { + info, err := abi.ReportSignerInfo(toSign) + if err != nil { + return nil, nil, err + } + si, err := abi.ParseSignerInfo(info) + if err != nil { + return nil, nil, err + } + var key *ecdsa.PrivateKey + switch si.SigningKey { + case abi.VcekReportSigner: + key = s.Keys.Vcek + case abi.VlekReportSigner: + key = s.Keys.Vlek + } h := crypto.SHA384.New() h.Write(toSign) - R, S, err := ecdsa.Sign(insecureRandomness, s.Keys.Vcek, h.Sum(nil)) + R, S, err := ecdsa.Sign(insecureRandomness, key, h.Sum(nil)) if err != nil { return nil, nil, err } @@ -104,16 +124,23 @@ type AmdSignerBuilder struct { Product string ArkCreationTime time.Time AskCreationTime time.Time + AsvkCreationTime time.Time VcekCreationTime time.Time + VlekCreationTime time.Time ArkCustom CertOverride AskCustom CertOverride + AsvkCustom CertOverride VcekCustom CertOverride + VlekCustom CertOverride + CSPID string HWID [abi.ChipIDSize]byte TCB kds.TCBVersion // Intermediate built certificates Ark *x509.Certificate Ask *x509.Certificate + Asvk *x509.Certificate Vcek *x509.Certificate + Vlek *x509.Certificate } func amdPkixName(commonName string, serialNumber string) pkix.Name { @@ -128,17 +155,42 @@ func amdPkixName(commonName string, serialNumber string) pkix.Name { } } -func unsignedArkOrAsk(issuerRole, subjectRole, productName string, issuerSerialNumber string, creationTime time.Time, expirationYears int) *x509.Certificate { +func arkName(product, serialNumber string) pkix.Name { + return amdPkixName(fmt.Sprintf("ARK-%s", product), serialNumber) +} + +func askName(product, serialNumber string) pkix.Name { + return amdPkixName(fmt.Sprintf("SEV-%s", product), serialNumber) +} + +func asvkName(product, serialNumber string) pkix.Name { + return amdPkixName(fmt.Sprintf("SEV-VLEK-%s", product), serialNumber) +} + +func (b *AmdSignerBuilder) unsignedRoot(arkName pkix.Name, key abi.ReportSigner, subjectSerial *big.Int, creationTime time.Time, expirationYears int) *x509.Certificate { + var subject pkix.Name + issuer := arkName cert := &x509.Certificate{} + crl := kds.CrlLinkByKey(b.Product, key) + sn := fmt.Sprintf("%x", subjectSerial) + switch key { + case abi.VcekReportSigner: + subject = askName(b.Product, sn) + case abi.VlekReportSigner: + subject = asvkName(b.Product, sn) + case abi.NoneReportSigner: + crl = kds.CrlLinkByKey(b.Product, abi.VcekReportSigner) + subject = arkName + } cert.NotBefore = creationTime cert.NotAfter = creationTime.Add(time.Duration(365*24*expirationYears) * time.Hour) cert.SignatureAlgorithm = x509.SHA384WithRSAPSS cert.PublicKeyAlgorithm = x509.RSA cert.Version = 3 - cert.SerialNumber = big.NewInt(0xc0dec0de) - cert.Issuer = amdPkixName(fmt.Sprintf("%s-%s", issuerRole, productName), issuerSerialNumber) - cert.Subject = amdPkixName(fmt.Sprintf("%s-%s", subjectRole, productName), fmt.Sprintf("%x", cert.SerialNumber)) - cert.CRLDistributionPoints = []string{fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/%s/crl", productName)} + cert.SerialNumber = subjectSerial + cert.Issuer = issuer + cert.Subject = subject + cert.CRLDistributionPoints = []string{crl} cert.IsCA = true cert.BasicConstraintsValid = true return cert @@ -194,6 +246,11 @@ func DefaultAsk() (*rsa.PrivateKey, error) { return privateKey, nil } +// DefaultAsvk returns a new RSA key with the expected size for an ASVK. +func DefaultAsvk() (*rsa.PrivateKey, error) { + return DefaultAsk() +} + // DefaultVcek returns a new ECDSA key on the expected curve for a VCEK. func DefaultVcek() (*ecdsa.PrivateKey, error) { privateKey, err := ecdsa.GenerateKey(elliptic.P384(), insecureRandomness) @@ -203,6 +260,11 @@ func DefaultVcek() (*ecdsa.PrivateKey, error) { return privateKey, nil } +// DefaultVlek returns a new ECDSA key on the expected curve for a VLEK. +func DefaultVlek() (*ecdsa.PrivateKey, error) { + return DefaultVcek() +} + // DefaultAmdKeys returns a key set for ARK, ASK, and VCEK with the expected key type and size. func DefaultAmdKeys() (*AmdKeys, error) { ark, err := DefaultArk() @@ -213,16 +275,25 @@ func DefaultAmdKeys() (*AmdKeys, error) { if err != nil { return nil, err } + asvk, err := DefaultAsvk() + if err != nil { + return nil, err + } vcek, err := DefaultVcek() if err != nil { return nil, err } - return &AmdKeys{Ark: ark, Ask: ask, Vcek: vcek}, nil + vlek, err := DefaultVlek() + if err != nil { + return nil, err + } + return &AmdKeys{Ark: ark, Ask: ask, Vcek: vcek, Vlek: vlek, Asvk: asvk}, nil } func (b *AmdSignerBuilder) certifyArk() error { - cert := unsignedArkOrAsk( - "ARK", "ARK", b.Product, "0xc0dec0de", b.ArkCreationTime, arkExpirationYears) + sn := big.NewInt(0xc0dec0de) + name := arkName(b.Product, fmt.Sprintf("%x", sn)) + cert := b.unsignedRoot(name, abi.NoneReportSigner, sn, b.ArkCreationTime, arkExpirationYears) cert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign b.ArkCustom.override(cert) @@ -236,8 +307,10 @@ func (b *AmdSignerBuilder) certifyArk() error { return err } +// must be called after certifyArk func (b *AmdSignerBuilder) certifyAsk() error { - cert := unsignedArkOrAsk("ARK", "SEV", b.Product, b.Ark.Subject.SerialNumber, b.AskCreationTime, askExpirationYears) + sn := big.NewInt(0xc0dec0de) + cert := b.unsignedRoot(b.Ark.Subject, abi.VcekReportSigner, sn, b.AskCreationTime, askExpirationYears) cert.KeyUsage = x509.KeyUsageCertSign b.AskCustom.override(cert) @@ -254,11 +327,37 @@ func (b *AmdSignerBuilder) certifyAsk() error { return err } -// CustomVcekExtensions returns an array of extensions following the KDS specification +// must be called after certifyArk +func (b *AmdSignerBuilder) certifyAsvk() error { + sn := big.NewInt(0xc0dec0de) + cert := b.unsignedRoot(b.Ark.Subject, abi.VlekReportSigner, sn, b.AsvkCreationTime, asvkExpirationYears) + cert.KeyUsage = x509.KeyUsageCertSign + + b.AsvkCustom.override(cert) + + caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Ark, b.Keys.Asvk.Public(), b.Keys.Ark) + if err != nil { + return fmt.Errorf("could not create a certificate from %v: %v", cert, err) + } + asvkcert, err := x509.ParseCertificate(caBytes) + if err != nil { + return err + } + b.Asvk = asvkcert + return err +} + +// CustomExtensions returns an array of extensions following the KDS specification // for the given values. -func CustomVcekExtensions(tcb kds.TCBParts, hwid [64]byte) []pkix.Extension { +func CustomExtensions(tcb kds.TCBParts, hwid []byte, cspid string) []pkix.Extension { + var productName []byte asn1Zero, _ := asn1.Marshal(0) - productName, _ := asn1.MarshalWithParams("Milan-B0", "ia5") + if hwid != nil { + productName, _ = asn1.MarshalWithParams("Milan-B0", "ia5") + } else { + // VLEK doesn't have a -stepping component to its productName. + productName, _ = asn1.MarshalWithParams("Milan", "ia5") + } blSpl, _ := asn1.Marshal(int(tcb.BlSpl)) teeSpl, _ := asn1.Marshal(int(tcb.TeeSpl)) snpSpl, _ := asn1.Marshal(int(tcb.SnpSpl)) @@ -267,8 +366,7 @@ func CustomVcekExtensions(tcb kds.TCBParts, hwid [64]byte) []pkix.Extension { spl6, _ := asn1.Marshal(int(tcb.Spl6)) spl7, _ := asn1.Marshal(int(tcb.Spl7)) ucodeSpl, _ := asn1.Marshal(int(tcb.UcodeSpl)) - asn1Hwid, _ := asn1.Marshal(hwid[:]) - return []pkix.Extension{ + exts := []pkix.Extension{ {Id: kds.OidStructVersion, Value: asn1Zero}, {Id: kds.OidProductName1, Value: productName}, {Id: kds.OidBlSpl, Value: blSpl}, @@ -279,24 +377,42 @@ func CustomVcekExtensions(tcb kds.TCBParts, hwid [64]byte) []pkix.Extension { {Id: kds.OidSpl6, Value: spl6}, {Id: kds.OidSpl7, Value: spl7}, {Id: kds.OidUcodeSpl, Value: ucodeSpl}, - {Id: kds.OidHwid, Value: asn1Hwid}, } + if hwid != nil { + asn1Hwid, _ := asn1.Marshal(hwid[:]) + exts = append(exts, pkix.Extension{Id: kds.OidHwid, Value: asn1Hwid}) + } else { + if cspid == "" { + cspid = "placeholder" + } + asn1cspid, _ := asn1.MarshalWithParams(cspid, "ia5") + exts = append(exts, pkix.Extension{Id: kds.OidCspID, Value: asn1cspid}) + } + return exts } -func (b *AmdSignerBuilder) certifyVcek() error { - cert := &x509.Certificate{} - cert.SignatureAlgorithm = x509.SHA384WithRSAPSS - cert.PublicKeyAlgorithm = x509.ECDSA - cert.Version = 3 - cert.Issuer = amdPkixName(fmt.Sprintf("SEV-%s", b.Product), b.Ask.Subject.SerialNumber) - cert.Subject = amdPkixName("SEV-VCEK", "0") - cert.SerialNumber = big.NewInt(0) - cert.Subject.SerialNumber = fmt.Sprintf("%x", cert.SerialNumber) - cert.NotBefore = time.Time{} - cert.NotAfter = b.VcekCreationTime.Add(vcekExpirationYears * 365 * 24 * time.Hour) - var hwid [64]byte - cert.ExtraExtensions = CustomVcekExtensions(kds.TCBParts{}, hwid) +func (b *AmdSignerBuilder) endorsementKeyPrecert(creationTime time.Time, hwid []byte, serialNumber *big.Int, key abi.ReportSigner) *x509.Certificate { + subject := amdPkixName(fmt.Sprintf("SEV-%s", key.String()), "0") + subject.SerialNumber = fmt.Sprintf("%x", serialNumber) + ica := b.Ask + if key == abi.VlekReportSigner { + ica = b.Asvk + } + return &x509.Certificate{ + Version: 3, + SignatureAlgorithm: x509.SHA384WithRSAPSS, + PublicKeyAlgorithm: x509.ECDSA, + Issuer: amdPkixName(fmt.Sprintf("SEV-%s", b.Product), ica.Subject.SerialNumber), + Subject: subject, + SerialNumber: serialNumber, + NotBefore: time.Time{}, + NotAfter: creationTime.Add(vcekExpirationYears * 365 * 24 * time.Hour), + ExtraExtensions: CustomExtensions(kds.TCBParts{}, hwid, b.CSPID), + } +} +func (b *AmdSignerBuilder) certifyVcek() error { + cert := b.endorsementKeyPrecert(b.VcekCreationTime, make([]byte, abi.ChipIDSize), big.NewInt(0), abi.VcekReportSigner) b.VcekCustom.override(cert) caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Ask, b.Keys.Vcek.Public(), b.Keys.Ask) @@ -308,8 +424,21 @@ func (b *AmdSignerBuilder) certifyVcek() error { return err } -// CertChain creates a test-only certificate chain from the keys and configurables in b. -func (b *AmdSignerBuilder) CertChain() (*AmdSigner, error) { +func (b *AmdSignerBuilder) certifyVlek() error { + cert := b.endorsementKeyPrecert(b.VlekCreationTime, nil, big.NewInt(0), abi.VlekReportSigner) + b.VlekCustom.override(cert) + + caBytes, err := x509.CreateCertificate(insecureRandomness, cert, b.Asvk, b.Keys.Vlek.Public(), b.Keys.Asvk) + if err != nil { + return fmt.Errorf("could not create a certificate from %v: %v", cert, err) + } + signed, err := x509.ParseCertificate(caBytes) + b.Vlek = signed + return err +} + +// TestOnlyCertChain creates a test-only certificate chain from the keys and configurables in b. +func (b *AmdSignerBuilder) TestOnlyCertChain() (*AmdSigner, error) { if b.Product == "" { b.Product = "Milan" // For terse tests. } @@ -326,13 +455,23 @@ func (b *AmdSignerBuilder) CertChain() (*AmdSigner, error) { if err := b.certifyAsk(); err != nil { return nil, fmt.Errorf("ask creation error: %v", err) } + if err := b.certifyAsvk(); err != nil { + return nil, fmt.Errorf("asvk creation error: %v", err) + } if err := b.certifyVcek(); err != nil { return nil, fmt.Errorf("vcek creation error: %v", err) } + if b.Keys.Vlek != nil { + if err := b.certifyVlek(); err != nil { + return nil, fmt.Errorf("vlek creation error: %v", err) + } + } s := &AmdSigner{ Ark: b.Ark, Ask: b.Ask, + Asvk: b.Asvk, Vcek: b.Vcek, + Vlek: b.Vlek, Keys: b.Keys, TCB: b.TCB, } @@ -340,8 +479,8 @@ func (b *AmdSignerBuilder) CertChain() (*AmdSigner, error) { return s, nil } -// DefaultCertChain creates a test-only certificate chain for a fake attestation signer. -func DefaultCertChain(productName string, creationTime time.Time) (*AmdSigner, error) { +// DefaultTestOnlyCertChain creates a test-only certificate chain for a fake attestation signer. +func DefaultTestOnlyCertChain(productName string, creationTime time.Time) (*AmdSigner, error) { keys, err := DefaultAmdKeys() if err != nil { return nil, fmt.Errorf("error generating fake keys: %v", err) @@ -349,17 +488,20 @@ func DefaultCertChain(productName string, creationTime time.Time) (*AmdSigner, e b := &AmdSignerBuilder{ Keys: keys, Product: productName, + CSPID: "go-sev-guest", ArkCreationTime: creationTime, AskCreationTime: creationTime, + AsvkCreationTime: creationTime, VcekCreationTime: creationTime, + VlekCreationTime: creationTime, } - return b.CertChain() + return b.TestOnlyCertChain() } // CertTableBytes outputs the certificates in AMD's ABI format. func (s *AmdSigner) CertTableBytes() ([]byte, error) { // Calculate the output size and the offset at which to copy each certificate. - headers := make([]abi.CertTableHeaderEntry, 4) // ARK, ASK, VCEK, NULL + headers := make([]abi.CertTableHeaderEntry, 6) // ARK, ASK, VCEK, VLEK, ASVK, NULL headers[0].GUID = uuid.Parse(abi.ArkGUID) headers[0].Offset = uint32(len(headers) * abi.CertTableEntrySize) headers[0].Length = uint32(len(s.Ark.Raw)) @@ -372,9 +514,17 @@ func (s *AmdSigner) CertTableBytes() ([]byte, error) { headers[2].Offset = headers[1].Offset + headers[1].Length headers[2].Length = uint32(len(s.Vcek.Raw)) + headers[3].GUID = uuid.Parse(abi.VlekGUID) + headers[3].Offset = headers[2].Offset + headers[2].Length + headers[3].Length = uint32(len(s.Vlek.Raw)) + + headers[4].GUID = uuid.Parse(abi.AsvkGUID) + headers[4].Offset = headers[3].Offset + headers[3].Length + headers[4].Length = uint32(len(s.Asvk.Raw)) + // Write out the headers and the certificates at the appropriate offsets. - result := make([]byte, headers[2].Offset+headers[2].Length) - for i, cert := range [][]byte{s.Ark.Raw, s.Ask.Raw, s.Vcek.Raw} { + result := make([]byte, headers[4].Offset+headers[4].Length) + for i, cert := range [][]byte{s.Ark.Raw, s.Ask.Raw, s.Vcek.Raw, s.Vlek.Raw, s.Asvk.Raw} { if err := (&headers[i]).Write(result[i*abi.CertTableEntrySize:]); err != nil { return nil, err } diff --git a/testing/fake_certs_test.go b/testing/fake_certs_test.go index 39f6de7..9cdc021 100644 --- a/testing/fake_certs_test.go +++ b/testing/fake_certs_test.go @@ -25,7 +25,7 @@ import ( ) func TestCertificatesParse(t *testing.T) { - signer, err := DefaultCertChain("Milan", time.Now()) + signer, err := DefaultTestOnlyCertChain("Milan", time.Now()) if err != nil { t.Fatal(err) } @@ -38,18 +38,26 @@ func TestCertificatesParse(t *testing.T) { t.Fatal(err) } var hasVcek bool + var hasVlek bool var hasAsk bool + var hasAsvk bool var hasArk bool - if len(entries) != 3 { - t.Errorf("ParseSnpCertTableHeader(_) returned %d entries, want 3", len(entries)) + if len(entries) != 5 { + t.Errorf("ParseSnpCertTableHeader(_) returned %d entries, want 5", len(entries)) } for _, entry := range entries { + if uuid.Equal(entry.GUID, uuid.Parse(abi.VlekGUID)) { + hasVlek = true + } if uuid.Equal(entry.GUID, uuid.Parse(abi.VcekGUID)) { hasVcek = true } if uuid.Equal(entry.GUID, uuid.Parse(abi.AskGUID)) { hasAsk = true } + if uuid.Equal(entry.GUID, uuid.Parse(abi.AsvkGUID)) { + hasAsvk = true + } if uuid.Equal(entry.GUID, uuid.Parse(abi.ArkGUID)) { hasArk = true } @@ -58,6 +66,9 @@ func TestCertificatesParse(t *testing.T) { t.Errorf("could not parse certificate of %v: %v", entry.GUID, err) } } + if !hasVlek { + t.Errorf("fake certs missing VLEK") + } if !hasVcek { t.Errorf("fake certs missing VCEK") } @@ -67,6 +78,9 @@ func TestCertificatesParse(t *testing.T) { if !hasArk { t.Errorf("fake certs missing ARK") } + if !hasAsvk { + t.Errorf("fake certs missing ASVK") + } if _, err := kds.VcekCertificateExtensions(signer.Vcek); err != nil { t.Errorf("could not parse generated VCEK extensions: %v", err) } diff --git a/testing/fakekds.go b/testing/fakekds.go index e7a03c0..c968026 100644 --- a/testing/fakekds.go +++ b/testing/fakekds.go @@ -16,7 +16,6 @@ package testing import ( "bytes" - _ "embed" "encoding/pem" "flag" "fmt" @@ -26,6 +25,7 @@ import ( "github.com/google/go-sev-guest/kds" kpb "github.com/google/go-sev-guest/proto/fakekds" + "github.com/google/go-sev-guest/verify/testdata" "github.com/google/go-sev-guest/verify/trust" "go.uber.org/multierr" "google.golang.org/protobuf/proto" @@ -61,29 +61,32 @@ func TestUseKDS() bool { return *testUseKDS || testKds.value == "amd" } -// 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. -// -//go:embed "milan.pem" -var milanCerts []byte - // Insert your own KDS cache here with go:embed. var internalKDSCache []byte +// RootBundle represents the two different CA bundles that the KDS can +// return. +type RootBundle struct { + VcekBundle string + VlekBundle string +} + // FakeKDS implements the verify.HTTPSGetter interface to provide certificates like AMD KDS, but // with certificates cached in a protobuf. type FakeKDS struct { Certs *kpb.Certificates - // Two CERTIFICATE PEMs for ASK, then ARK, per product - RootBundles map[string]string + // Two CERTIFICATE PEMs for ASK, then ARK or ASVK then ARK, per product + RootBundles map[string]RootBundle } // FakeKDSFromFile returns a FakeKDS from a path to a serialized fakekds.Certificates message. func FakeKDSFromFile(path string) (*FakeKDS, error) { result := &FakeKDS{ - Certs: &kpb.Certificates{}, - RootBundles: map[string]string{"Milan": string(milanCerts)}, + Certs: &kpb.Certificates{}, + RootBundles: map[string]RootBundle{"Milan": { + VcekBundle: string(testdata.MilanVcekBytes), + VlekBundle: string(testdata.MilanVlekBytes), + }}, } contents, err := os.ReadFile(path) @@ -117,11 +120,26 @@ func FakeKDSFromSigner(signer *AmdSigner) (*FakeKDS, error) { pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ask.Raw}), pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ark.Raw}), ); err != nil { - return nil, fmt.Errorf("could not encode root certificates: %v", err) + return nil, fmt.Errorf("could not encode VCEK root certificates: %v", err) + } + vcekBundle := b.String() + var vlekBundle string + if signer.Asvk != nil { + b := &strings.Builder{} + if err := multierr.Combine( + pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Asvk.Raw}), + pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Ark.Raw}), + ); err != nil { + return nil, fmt.Errorf("could not encode VLEK root certificates: %v", err) + } + vlekBundle = b.String() } return &FakeKDS{ - Certs: certs, - RootBundles: map[string]string{"Milan": b.String()}, + Certs: certs, + RootBundles: map[string]RootBundle{"Milan": { + VcekBundle: vcekBundle, + VlekBundle: vlekBundle, + }}, }, nil } @@ -140,13 +158,20 @@ func FindChipTcbCerts(database *kpb.Certificates, chipID []byte) map[uint64][]by // database. func (f *FakeKDS) Get(url string) ([]byte, error) { // If a root cert request, return the embedded default root certs. - product, err := kds.ParseProductCertChainURL(url) + product, key, err := kds.ParseProductCertChainURL(url) if err == nil { - bundle, ok := f.RootBundles[product] + bundles, ok := f.RootBundles[product] if !ok { return nil, fmt.Errorf("no embedded CA bundle for product %q", product) } - return []byte(bundle), nil + switch key { + case kds.VcekCertFunction: + return []byte(bundles.VcekBundle), nil + case kds.VlekCertFunction: + return []byte(bundles.VlekBundle), nil + default: + return nil, fmt.Errorf("internal: unsupperted key type for fake bundles: %q", key) + } } vcek, err := kds.ParseVCEKCertURL(url) if err != nil { @@ -170,8 +195,11 @@ func GetKDS(t testing.TB) trust.HTTPSGetter { return trust.DefaultHTTPSGetter() } fakeKds := &FakeKDS{ - Certs: &kpb.Certificates{}, - RootBundles: map[string]string{"Milan": string(milanCerts)}, + Certs: &kpb.Certificates{}, + RootBundles: map[string]RootBundle{"Milan": { + VcekBundle: string(testdata.MilanVcekBytes), + VlekBundle: string(testdata.MilanVlekBytes), + }}, } // Provide nothing if --test_kds=none. if testKds.value == "none" { diff --git a/testing/test_cases.go b/testing/test_cases.go index 81c40fe..ecb584c 100644 --- a/testing/test_cases.go +++ b/testing/test_cases.go @@ -49,6 +49,17 @@ var userZeros11 = [64]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1} +// userZeros12 defines a ReportData example that is all zeros except the last 2 bytes are 1, 2. +var userZeros12 = [64]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2} + // zeroReport is a textproto representing an unsigned report response to UserZeros. // The policy just sets the debug bit and bit 17 to 1, and the signature algo 1 is the encoding for // ECDSA P-384 with SHA-348. Every `bytes` field needs to be the correct length. @@ -87,19 +98,53 @@ var oneReport = ` signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ` +// vlekReport is a textproto representing an unsigned report response to UserZeros12 and a VLEK. +var vlekReport = ` +version: 2 +policy: 0xa0000 +signature_algo: 1 +signer_info: 4 +report_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02' +family_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +image_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +measurement: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +host_data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +id_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +author_key_digest: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +report_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +report_id_ma: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +chip_id: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + signature: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +` + +// TestReportOptions represents a few configurables for generating fake reports from particular inputs. +type TestReportOptions struct { + ReportData []byte + SignerInfo abi.SignerInfo +} + // TestRawReport creates simple raw attestation report with the given REPORT_DATA. // We can't sign the report with AMD keys, and verification isn't the client's responsibility, so // we keep the signature zeros. // Similarly, we leave the randomly-generated fields zero. func TestRawReport(reportData [64]byte) [labi.SnpReportRespReportSize]byte { + return CreateRawReport(&TestReportOptions{ReportData: reportData[:]}) +} + +// CreateRawReport creates simple raw attestation report with the given configurable data in options. +// We can't sign the report with AMD keys, and verification isn't the client's responsibility, so +// we keep the signature zeros. +// Similarly, we leave the randomly-generated fields zero. +func CreateRawReport(opts *TestReportOptions) [labi.SnpReportRespReportSize]byte { var r [labi.SnpReportRespReportSize]byte // Set Version to 2 binary.LittleEndian.PutUint32(r[0x00:0x04], 2) binary.LittleEndian.PutUint64(r[0x08:0x10], abi.SnpPolicyToBytes(abi.SnpPolicy{Debug: true})) // Signature algorithm ECC P-384 with SHA-384 is encoded as 1. binary.LittleEndian.PutUint32(r[0x34:0x38], 1) + binary.LittleEndian.PutUint32(r[0x48:0x4C], abi.ComposeSignerInfo(opts.SignerInfo)) // Place user data in its report location. - copy(r[0x50:0x90], reportData[:]) + copy(r[0x50:0x90], opts.ReportData) return r } @@ -113,7 +158,7 @@ type DeviceOptions struct { func makeTestCerts(opts *DeviceOptions) ([]byte, *AmdSigner, error) { signer := opts.Signer if signer == nil { - s, err := DefaultCertChain("Milan", opts.Now) + s, err := DefaultTestOnlyCertChain("Milan", opts.Now) if err != nil { return nil, nil, err } @@ -126,6 +171,13 @@ func makeTestCerts(opts *DeviceOptions) ([]byte, *AmdSigner, error) { return certs, signer, nil } +type KeyChoice int + +const ( + KeyChoiceVcek = iota + KeyChoiceVlek +) + // TestCase represents a get_report input/output test case. type TestCase struct { Name string @@ -134,6 +186,7 @@ type TestCase struct { OutputProto string FwErr abi.SevFirmwareStatus EsResult labi.EsResult + EK KeyChoice WantErr string } @@ -141,6 +194,10 @@ type TestCase struct { func TestCases() []TestCase { zeroRaw := TestRawReport(userZeros) oneRaw := TestRawReport(userZeros1) + vlekRaw := CreateRawReport(&TestReportOptions{ + ReportData: userZeros12[:], + SignerInfo: abi.SignerInfo{SigningKey: abi.VlekReportSigner}, + }) return []TestCase{ { Name: "zeros", @@ -154,6 +211,13 @@ func TestCases() []TestCase { Output: oneRaw, OutputProto: oneReport, }, + { + Name: "zeros, 1, 2 in VLEK", + Input: userZeros12, + Output: vlekRaw, + OutputProto: vlekReport, + EK: KeyChoiceVlek, + }, { Name: "fw oom", Input: userZeros11, diff --git a/tools/check/check.go b/tools/check/check.go index e7aecdf..d004328 100644 --- a/tools/check/check.go +++ b/tools/check/check.go @@ -535,8 +535,11 @@ func main() { die(fmt.Errorf("could not read %q: %v", *testKdsFile, err)) } kds := &testing.FakeKDS{ - Certs: &kpb.Certificates{}, - RootBundles: map[string]string{"Milan": string(testdata.MilanBytes)}, + Certs: &kpb.Certificates{}, + RootBundles: map[string]testing.RootBundle{"Milan": { + VcekBundle: string(testdata.MilanVcekBytes), + VlekBundle: string(testdata.MilanVlekBytes), + }}, } sopts.Getter = kds if err := proto.Unmarshal(b, kds.Certs); err != nil { diff --git a/tools/check/check_test.go b/tools/check/check_test.go index 2a9440d..4f86f15 100644 --- a/tools/check/check_test.go +++ b/tools/check/check_test.go @@ -459,7 +459,7 @@ func TestCheckBadFlagOverridesGoodField(t *testing.T) { } func TestCaBundles(t *testing.T) { - signer, err := fakesev.DefaultCertChain("Milan", time.Now()) + signer, err := fakesev.DefaultTestOnlyCertChain("Milan", time.Now()) if err != nil { t.Fatal(err) } diff --git a/validate/validate.go b/validate/validate.go index 0b978d8..31c0112 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -315,10 +315,10 @@ type reportTcbDescriptions struct { launch partDescription // The TCB that the VCEK certificate is certified for. Embedded as x509v3 extensions from // AMD's Key Distribution Service (KDS). - vcek partDescription + cert partDescription } -func getReportTcbs(report *spb.Report, vcekTcb kds.TCBVersion) *reportTcbDescriptions { +func getReportTcbs(report *spb.Report, certTcb kds.TCBVersion) *reportTcbDescriptions { return &reportTcbDescriptions{ reported: partDescription{ parts: kds.DecomposeTCBVersion(kds.TCBVersion(report.GetReportedTcb())), @@ -336,9 +336,9 @@ func getReportTcbs(report *spb.Report, vcekTcb kds.TCBVersion) *reportTcbDescrip parts: kds.DecomposeTCBVersion(kds.TCBVersion(report.GetLaunchTcb())), desc: "report's LAUNCH_TCB", }, - vcek: partDescription{ - parts: kds.DecomposeTCBVersion(vcekTcb), - desc: "TCB of the VCEK certificate", + cert: partDescription{ + parts: kds.DecomposeTCBVersion(certTcb), + desc: "TCB of the V[CL]EK certificate", }, } } @@ -384,11 +384,11 @@ func tcbGtError(wantLower, wantHigher partDescription) error { wantHigher.desc, wantHigher.parts, wantLower.desc, wantLower.parts) } -// validateTcb returns an error if the TCB values present in the report and VCEK certificate do not +// validateTcb returns an error if the TCB values present in the report and V[CL]EK certificate do not // obey expected relationships with respect to the given validation policy, or with respect to // internal consistency checks. -func validateTcb(report *spb.Report, vcekTcb kds.TCBVersion, options *Options) error { - reportTcbs := getReportTcbs(report, vcekTcb) +func validateTcb(report *spb.Report, certTcb kds.TCBVersion, options *Options) error { + reportTcbs := getReportTcbs(report, certTcb) policyTcbs := getPolicyTcbs(options) var provisionalErr error @@ -400,17 +400,18 @@ func validateTcb(report *spb.Report, vcekTcb kds.TCBVersion, options *Options) e return multierr.Combine(provisionalErr, tcbGtError(policyTcbs.minLaunch, reportTcbs.launch), - // Any change to the TCB means that the VCEK certificate at an earlier TCB is no longer valid. The - // host must make sure that the up-to-date certificate is provisioned and delivered alongside the - // report that contains the new reported TCB value. - // If the certificate's TCB is greater than the report's TCB, then the host has not provisioned - // a certificate for the machine's actual state and should also not be accepted. - tcbNeError(reportTcbs.reported, reportTcbs.vcek), - tcbGtError(reportTcbs.vcek, reportTcbs.current), + // Any change to the TCB means that the V[CL]EK certificate at an earlier TCB is no + // longer valid. The host must make sure that the up-to-date certificate is provisioned + // and delivered alongside the report that contains the new reported TCB value. + // If the certificate's TCB is greater than the report's TCB, then the host has not + // provisioned a certificate for the machine's actual state and should also not be + // accepted. + tcbNeError(reportTcbs.reported, reportTcbs.cert), + tcbGtError(reportTcbs.cert, reportTcbs.current), tcbGtError(policyTcbs.minimum, reportTcbs.reported)) // Note: // * by transitivity of <=, if we're here, then minimum <= current - // * since vcek == reported, reported <= current + // * since cert == reported, reported <= current // Checks that could make sense but don't: // @@ -542,7 +543,11 @@ func consolidateKeyHashes(options *Options) error { } func validateKeys(report *spb.Report, options *Options) error { - if options.RequireAuthorKey && report.GetAuthorKeyEn() == 0 { + info, err := abi.ParseSignerInfo(report.GetSignerInfo()) + if err != nil { + return err + } + if options.RequireAuthorKey && !info.AuthorKeyEn { return errors.New("author key missing when required") } @@ -565,7 +570,7 @@ func validateKeys(report *spb.Report, options *Options) error { return false } - authorKeyTrusted := report.GetAuthorKeyEn() != 0 && bytesContained(options.TrustedAuthorKeyHashes, + authorKeyTrusted := info.AuthorKeyEn && bytesContained(options.TrustedAuthorKeyHashes, report.GetAuthorKeyDigest()) if options.RequireAuthorKey && !authorKeyTrusted { @@ -580,15 +585,42 @@ func validateKeys(report *spb.Report, options *Options) error { return nil } -func validateSnpAttestation(report *spb.Report, vcek []byte, options *Options) error { - vcekCert, err := x509.ParseCertificate(vcek) +func validateKeyKind(report *spb.Attestation) (*x509.Certificate, error) { + info, err := abi.ParseSignerInfo(report.GetReport().GetSignerInfo()) + if err != nil { + return nil, err + } + switch info.SigningKey { + case abi.VcekReportSigner: + if report.GetCertificateChain().VcekCert != nil { + return x509.ParseCertificate(report.GetCertificateChain().VcekCert) + } + case abi.VlekReportSigner: + if report.GetCertificateChain().VlekCert != nil { + return x509.ParseCertificate(report.GetCertificateChain().VlekCert) + } + case abi.NoneReportSigner: + return nil, nil + } + return nil, fmt.Errorf("unsupported key kind %v", info.SigningKey) +} + +// SnpAttestation validates fields of the protobuf representation of an attestation report against +// expectations. Does not check the attestation certificates or signature. +func SnpAttestation(attestation *spb.Attestation, options *Options) error { + endorsementKeyCert, err := validateKeyKind(attestation) if err != nil { - return fmt.Errorf("could not parse VCEK certificate: %v", err) + return err } - // Get the TCB values of the VCEK - exts, err := kds.VcekCertificateExtensions(vcekCert) + report := attestation.GetReport() + info, err := abi.ParseSignerInfo(report.GetSignerInfo()) if err != nil { - return fmt.Errorf("could not get VCEK certificate extensions: %v", err) + return err + } + // Get the TCB values of the V[CL]EK + exts, err := kds.CertificateExtensions(endorsementKeyCert, info.SigningKey) + if err != nil { + return fmt.Errorf("could not get %v certificate extensions: %v", info.SigningKey, err) } if report.GetGuestSvn() < options.MinimumGuestSvn { @@ -611,20 +643,13 @@ func validateSnpAttestation(report *spb.Report, vcek []byte, options *Options) e } // MaskChipId might be 1 for the host, so only check if the the CHIP_ID is not all zeros. - if !allZero(report.GetChipId()) && !bytes.Equal(report.GetChipId(), exts.HWID[:]) { + if info.SigningKey == abi.VcekReportSigner && !allZero(report.GetChipId()) && !bytes.Equal(report.GetChipId(), exts.HWID[:]) { return fmt.Errorf("report field CHIP_ID %s is not the same as the VCEK certificate's HWID %s", hex.EncodeToString(report.GetChipId()), hex.EncodeToString(exts.HWID[:])) } return nil } -// SnpAttestation validates fields of the protobuf representation of an attestation report against -// expectations. Does not check the attestation certificates or signature. -func SnpAttestation(attestation *spb.Attestation, options *Options) error { - return validateSnpAttestation(attestation.GetReport(), - attestation.GetCertificateChain().GetVcekCert(), options) -} - // RawSnpAttestation validates fields of a raw attestation report against expectations. Does not // check the attestation certificates or signature. func RawSnpAttestation(report []byte, certTable []byte, options *Options) error { @@ -633,14 +658,10 @@ func RawSnpAttestation(report []byte, certTable []byte, options *Options) error return fmt.Errorf("could not unmarshal SNP certificate table: %v", err) } - vcek, err := certs.GetByGUIDString(abi.VcekGUID) - if err != nil { - return fmt.Errorf("could not get VCEK certificate: %v", err) - } - proto, err := abi.ReportToProto(report) if err != nil { return fmt.Errorf("could not parse attestation report: %v", err) } - return validateSnpAttestation(proto, vcek, options) + return SnpAttestation(&spb.Attestation{Report: proto, CertificateChain: certs.Proto()}, + options) } diff --git a/validate/validate_test.go b/validate/validate_test.go index 30cdc59..fcd9f10 100644 --- a/validate/validate_test.go +++ b/validate/validate_test.go @@ -67,7 +67,7 @@ func TestValidateSnpAttestation(t *testing.T) { reportedTcb kds.TCBParts committedTcb kds.TCBParts launchTcb kds.TCBParts - authorKeyEn uint32 + signerInfo abi.SignerInfo currentBuild uint8 currentMajor uint8 currentMinor uint8 @@ -100,7 +100,7 @@ func TestValidateSnpAttestation(t *testing.T) { ReportId: reportID, ReportIdMa: reportIDMA, ChipId: chipID[:], - AuthorKeyEn: opts.authorKeyEn, + SignerInfo: abi.ComposeSignerInfo(opts.signerInfo), CommittedBuild: uint32(opts.committedBuild), CommittedMajor: uint32(opts.committedMajor), CommittedMinor: uint32(opts.committedMinor), @@ -129,7 +129,7 @@ func TestValidateSnpAttestation(t *testing.T) { t.Fatal(err) } now := time.Now() - sign0, err := test.DefaultCertChain("Milan", now) + sign0, err := test.DefaultTestOnlyCertChain("Milan", now) if err != nil { t.Fatal(err) } @@ -139,14 +139,23 @@ func TestValidateSnpAttestation(t *testing.T) { ArkCreationTime: now, AskCreationTime: now, VcekCreationTime: now, + VlekCreationTime: now, VcekCustom: test.CertOverride{ - Extensions: test.CustomVcekExtensions( + Extensions: test.CustomExtensions( goodtcb, - chipID, + chipID[:], + "", + ), + }, + VlekCustom: test.CertOverride{ + Extensions: test.CustomExtensions( + goodtcb, + nil, + "Cloud Service Provider", ), }, } - sign, err := sb.CertChain() + sign, err := sb.TestOnlyCertChain() if err != nil { t.Fatal(err) } @@ -168,7 +177,7 @@ func TestValidateSnpAttestation(t *testing.T) { committedTcb: goodtcb, reportedTcb: goodtcb, launchTcb: goodtcb, - authorKeyEn: 1, + signerInfo: abi.SignerInfo{AuthorKeyEn: true}, currentBuild: 2, committedBuild: 2, currentMajor: 1, @@ -195,7 +204,7 @@ func TestValidateSnpAttestation(t *testing.T) { Input: nonce54321, Output: func() [labi.SnpReportRespReportSize]byte { opts := baseOpts - opts.authorKeyEn = 0 + opts.signerInfo = abi.SignerInfo{} return makeReport(nonce54321, opts) }(), }, diff --git a/testing/milan.pem b/verify/testdata/milanvlek.testcer similarity index 55% rename from testing/milan.pem rename to verify/testdata/milanvlek.testcer index 148ca46..fd5ff86 100644 --- a/testing/milan.pem +++ b/verify/testdata/milanvlek.testcer @@ -1,39 +1,40 @@ -----BEGIN CERTIFICATE----- -MIIGiTCCBDigAwIBAgIDAQABMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC +MIIGjzCCBD6gAwIBAgIDAQEBMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp -Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTgyNDIwWhcNNDUxMDIy -MTgyNDIwWjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS -BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j -ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJU0VWLU1pbGFuMIICIjANBgkqhkiG -9w0BAQEFAAOCAg8AMIICCgKCAgEAnU2drrNTfbhNQIllf+W2y+ROCbSzId1aKZft -2T9zjZQOzjGccl17i1mIKWl7NTcB0VYXt3JxZSzOZjsjLNVAEN2MGj9TiedL+Qew -KZX0JmQEuYjm+WKksLtxgdLp9E7EZNwNDqV1r0qRP5tB8OWkyQbIdLeu4aCz7j/S -l1FkBytev9sbFGzt7cwnjzi9m7noqsk+uRVBp3+In35QPdcj8YflEmnHBNvuUDJh -LCJMW8KOjP6++Phbs3iCitJcANEtW4qTNFoKW3CHlbcSCjTM8KsNbUx3A8ek5EVL -jZWH1pt9E3TfpR6XyfQKnY6kl5aEIPwdW3eFYaqCFPrIo9pQT6WuDSP4JCYJbZne -KKIbZjzXkJt3NQG32EukYImBb9SCkm9+fS5LZFg9ojzubMX3+NkBoSXI7OPvnHMx -jup9mw5se6QUV7GqpCA2TNypolmuQ+cAaxV7JqHE8dl9pWf+Y3arb+9iiFCwFt4l -AlJw5D0CTRTC1Y5YWFDBCrA/vGnmTnqG8C+jjUAS7cjjR8q4OPhyDmJRPnaC/ZG5 -uP0K0z6GoO/3uen9wqshCuHegLTpOeHEJRKrQFr4PVIwVOB0+ebO5FgoyOw43nyF -D5UKBDxEB4BKo/0uAiKHLRvvgLbORbU8KARIs1EoqEjmF8UtrmQWV2hUjwzqwvHF -ei8rPxMCAwEAAaOBozCBoDAdBgNVHQ4EFgQUO8ZuGCrD/T1iZEib47dHLLT8v/gw -HwYDVR0jBBgwFoAUhawa0UP3yKxV1MUdQUir1XhK1FMwEgYDVR0TAQH/BAgwBgEB -/wIBADAOBgNVHQ8BAf8EBAMCAQQwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cHM6Ly9r -ZHNpbnRmLmFtZC5jb20vdmNlay92MS9NaWxhbi9jcmwwRgYJKoZIhvcNAQEKMDmg -DzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIFAKID -AgEwowMCAQEDggIBAIgeUQScAf3lDYqgWU1VtlDbmIN8S2dC5kmQzsZ/HtAjQnLE -PI1jh3gJbLxL6gf3K8jxctzOWnkYcbdfMOOr28KT35IaAR20rekKRFptTHhe+DFr -3AFzZLDD7cWK29/GpPitPJDKCvI7A4Ug06rk7J0zBe1fz/qe4i2/F12rvfwCGYhc -RxPy7QF3q8fR6GCJdB1UQ5SlwCjFxD4uezURztIlIAjMkt7DFvKRh+2zK+5plVGG -FsjDJtMz2ud9y0pvOE4j3dH5IW9jGxaSGStqNrabnnpF236ETr1/a43b8FFKL5QN -mt8Vr9xnXRpznqCRvqjr+kVrb6dlfuTlliXeQTMlBoRWFJORL8AcBJxGZ4K2mXft -l1jU5TLeh5KXL9NW7a/qAOIUs2FiOhqrtzAhJRg9Ij8QkQ9Pk+cKGzw6El3T3kFr -Eg6zkxmvMuabZOsdKfRkWfhH2ZKcTlDfmH1H0zq0Q2bG3uvaVdiCtFY1LlWyB38J -S2fNsR/Py6t5brEJCFNvzaDky6KeC4ion/cVgUai7zzS3bGQWzKDKU35SqNU2WkP -I8xCZ00WtIiKKFnXWUQxvlKmmgZBIYPe01zD0N8atFxmWiSnfJl690B9rJpNR/fI -ajxCW3Seiws6r1Zm+tCuVbMiNtpS9ThjNX4uve5thyfE2DgoxRFvY1CsoF5M +Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjIxMTE2MjI0NTI0WhcNNDcxMTE2 +MjI0NTI0WjCBgDEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQw +EgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFu +Y2VkIE1pY3JvIERldmljZXMxFzAVBgNVBAMMDlNFVi1WTEVLLU1pbGFuMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1EUWkz5FTPz+uWT2hCEyisam8FRu +XZAmS3l+rXgSCeS1Q0+1olcnFSJpiwfssfhoutJqePyicu+OhkX131PMeO/VOtH3 +upK4YNJmq36IJp7ZWIm5nK2fJNkYEHW0m/NXcIA9U2iHl5bAQ5cbGp97/FaOJ4Vm +GoTMV658Yox/plFmZRFfRcsw2hyNhqUl1gzdpnIIgPkygUovFEgaa0IVSgGLHQhZ +QiebNLLSVWRVReve0t94zlRIRRdrz84cckP9H9DTAUMyQaxSZbPINKbV6TPmtrwA +V9UP1Qq418xn9I+C0SsWutP/5S1OiL8OTzQ4CvgbHOfd2F3yVv4xDBza4SelF2ig +oDf+BF4XI/IIHJL2N5uKy3+gkSB2Xl6prohgVmqRFvBW9OTCEa32WhXu0t1Z1abE +KDZ3LpZt9/Crg6zyPpXDLR/tLHHpSaPRj7CTzHieKMTz+Q6RrCCQcHGfaAD/ETNY +56aHvNJRZgbzXDUJvnLr3dYyOvvn/DtKhCSimJynn7Len4ArDVQVwXRPe3hR/asC +E2CajT7kGC1AOtUzQuIKZS2D0Qk74g297JhLHpEBlQiyjRJ+LCWZNx9uJcixGyza +v6fiOWx4U8uWhRzHs8nvDAdcS4LW31tPlA9BeOK/BGimQTu7hM5MDFZL0C9dWK5p +uCUJex6I2vSqvycCAwEAAaOBozCBoDAdBgNVHQ4EFgQUNuJXE6qi45/CgqkKRPtV +LObC7pEwHwYDVR0jBBgwFoAUhawa0UP3yKxV1MUdQUir1XhK1FMwEgYDVR0TAQH/ +BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQQwOgYDVR0fBDMwMTAvoC2gK4YpaHR0 +cHM6Ly9rZHNpbnRmLmFtZC5jb20vdmxlay92MS9NaWxhbi9jcmwwRgYJKoZIhvcN +AQEKMDmgDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQME +AgIFAKIDAgEwowMCAQEDggIBAI7ayEXDNj1rCVnjQFb6L91NNOmEIOmi6XtopAqr +8fj7wqXap1MY82Y0AIi1K9R7C7G1sCmY8QyEyX0zqHsoNbU2IMcSdZrIp8neT8af +v8tPt7qoW3hZ+QQRMtgVkVVrjJZelvlB74xr5ifDcDiBd2vu/C9IqoQS4pVBKNSF +pofzjtYKvebBBBXxeM2b901UxNgVjCY26TtHEWN9cA6cDVqDDCCL6uOeR9UOvKDS +SqlM6nXldSj7bgK7Wh9M9587IwRvNZluXc1CDiKMZybLdSKOlyMJH9ss1GPn0eBV +EhVjf/gttn7HrcQ9xJZVXyDtL3tkGzemrPK14NOYzmph6xr1iiedAzOVpNdPiEXn +2lvas0P4TD9UgBh0Y7xyf2yENHiSgJT4T8Iktm/TSzuh4vqkQ72A1HdNTGjoZcfz +KCsQJ/YuFICeaNxw5cIAGBK/o+6Ek32NPv5XtixNOhEx7GsaVRG05bq5oTt14b4h +KYhqV1CDrX5hiVRpFFDs/sAGfgTzLdiGXLcvYAUz1tCKIT/eQS9c4/yitn4F3mCP +d4uQB+fggMtK0qPRthpFtc2SqVCTvHnhxyXqo7GpXMsssgLgKNwaFPe2+Ld5OwPR +6Pokji9h55m05Dxob8XtD4gW6oFLo9Icg7XqdOr9Iip5RBIPxy7rKk/ReqGs9KH7 +0YPk -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC diff --git a/verify/testdata/testdata.go b/verify/testdata/testdata.go index c9db280..462d200 100644 --- a/verify/testdata/testdata.go +++ b/verify/testdata/testdata.go @@ -25,10 +25,15 @@ import _ "embed" //go:embed vcek.testcer var VcekBytes []byte -// MilanBytes is the Milan product cert_chain as issued by the AMD KDS. +// MilanVcekBytes is the Milan product vcek cert_chain as issued by the AMD KDS. // //go:embed milan.testcer -var MilanBytes []byte +var MilanVcekBytes []byte + +// MilanVlekBytes is the Milan product vlek cert_chain as issued by the AMD KDS. +// +//go:embed milanvlek.testcer +var MilanVlekBytes []byte // AttestationBytes is an example attestation report from a VM that was // launched without an ID_BLOCK. diff --git a/verify/trust/trust.go b/verify/trust/trust.go index ee88586..fdc46da 100644 --- a/verify/trust/trust.go +++ b/verify/trust/trust.go @@ -24,6 +24,7 @@ import ( "io" "net/http" "os" + "strings" "sync" "time" @@ -43,7 +44,7 @@ var ( // is over cannot be reconstructed from the SEV certificate format. The X.509 certificate with its // expiration dates is at https://kdsintf.amd.com/vcek/v1/Milan/cert_chain //go:embed ask_ark_milan.sevcert - askArkMilanBytes []byte + askArkMilanVcekBytes []byte // A cache of product certificate KDS results per product. prodCacheMu sync.Mutex @@ -56,8 +57,9 @@ const initialDelay = 10 * time.Second // ProductCerts contains the root key and signing key devoted to a given product line. type ProductCerts struct { - Ask *x509.Certificate - Ark *x509.Certificate + Ask *x509.Certificate + Asvk *x509.Certificate + Ark *x509.Certificate } // AMDRootCerts encapsulates the certificates that represent root of trust in AMD. @@ -164,7 +166,7 @@ func DefaultHTTPSGetter() HTTPSGetter { func (r *AMDRootCerts) Unmarshal(data []byte) error { ask, index, err := abi.ParseAskCert(data) if err != nil { - return fmt.Errorf("could not parse ASK certificate in SEV certificate format: %v", err) + return fmt.Errorf("could not parse intermediate ASK certificate in SEV certificate format: %v", err) } r.AskSev = ask ark, _, err := abi.ParseAskCert(data[index:]) @@ -189,13 +191,17 @@ func ParseCert(cert []byte) (*x509.Certificate, error) { return x509.ParseCertificate(raw) } -// Decode populates the ProductCerts from DER-formatted certificates for both the ASK and the ARK. +// Decode populates the ProductCerts from DER-formatted certificates for both the AS[V]K and the ARK. func (r *ProductCerts) Decode(ask []byte, ark []byte) error { - askCert, err := ParseCert(ask) + ica, err := ParseCert(ask) if err != nil { - return fmt.Errorf("could not parse ASK certificate: %v", err) + return fmt.Errorf("could not parse intermediate certificate: %v", err) + } + if strings.HasPrefix(ica.Subject.CommonName, "SEV-VLEK") { + r.Asvk = ica + } else { + r.Ask = ica } - r.Ask = askCert arkCert, err := ParseCert(ark) if err != nil { @@ -226,16 +232,28 @@ func (r *ProductCerts) FromKDSCert(path string) error { return r.FromKDSCertBytes(certBytes) } -// X509Options returns the ASK and ARK as the only intermediate and root certificates of an x509 +// X509Options returns the AS[V]K 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 *ProductCerts) X509Options(now time.Time) *x509.VerifyOptions { - if r.Ask == nil || r.Ark == nil { +// The choice between ASK and ASVK is determined bey key. +func (r *ProductCerts) X509Options(now time.Time, key abi.ReportSigner) *x509.VerifyOptions { + if r.Ark == nil { return nil } roots := x509.NewCertPool() roots.AddCert(r.Ark) intermediates := x509.NewCertPool() - intermediates.AddCert(r.Ask) + switch key { + case abi.VcekReportSigner: + if r.Ask == nil { + return nil + } + intermediates.AddCert(r.Ask) + case abi.VlekReportSigner: + if r.Asvk == nil { + return nil + } + intermediates.AddCert(r.Asvk) + } return &x509.VerifyOptions{Roots: roots, Intermediates: intermediates, CurrentTime: now} } @@ -249,7 +267,7 @@ func ClearProductCertCache() { // 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) { +func GetProductChain(product string, s abi.ReportSigner, getter HTTPSGetter) (*ProductCerts, error) { if productCertCache == nil { prodCacheMu.Lock() productCertCache = make(map[string]*ProductCerts) @@ -257,7 +275,7 @@ func GetProductChain(product string, getter HTTPSGetter) (*ProductCerts, error) } result, ok := productCertCache[product] if !ok { - askark, err := getter.Get(kds.ProductCertChainURL(product)) + askark, err := getter.Get(kds.ProductCertChainURL(s, product)) if err != nil { return nil, &AttestationRecreationErr{ Msg: fmt.Sprintf("could not download ASK and ARK certificates: %v", err), @@ -309,19 +327,20 @@ func (r *AMDRootCerts) FromKDSCert(path string) error { return r.ProductCerts.FromKDSCert(path) } -// X509Options returns the ASK and ARK as the only intermediate and root certificates of an x509 +// X509Options returns the AS[V]K 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(now time.Time) *x509.VerifyOptions { +// Choice between ASK and ASVK is determined by key. +func (r *AMDRootCerts) X509Options(now time.Time, key abi.ReportSigner) *x509.VerifyOptions { if r.ProductCerts == nil { return nil } - return r.ProductCerts.X509Options(now) + return r.ProductCerts.X509Options(now, key) } // Parse ASK, ARK certificates from the embedded AMD certificate file. func init() { milanCerts := new(AMDRootCerts) - milanCerts.Unmarshal(askArkMilanBytes) + milanCerts.Unmarshal(askArkMilanVcekBytes) DefaultRootCerts = map[string]*AMDRootCerts{ "Milan": milanCerts, } diff --git a/verify/verify.go b/verify/verify.go index c01b1ea..d74bffb 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -35,12 +35,19 @@ import ( ) const ( - askVersion = 1 - askKeyUsage = 0x13 - arkVersion = 1 - arkKeyUsage = 0x0 - askX509Version = 3 - arkX509Version = 3 + askVersion = 1 + askKeyUsage = 0x13 + arkVersion = 1 + arkKeyUsage = 0x0 + askX509Version = 3 + asvkX509Version = 3 + arkX509Version = 3 +) + +var ( + // ErrMissingVlek is returned when attempting to verify a VLEK-signed report that doesn't also + // have its VLEK certificate attached. + ErrMissingVlek = errors.New("report signed with VLEK, but VLEK certificate is missing") ) func askVerifiedBy(signee, signer *abi.AskCert, signeeName, signerName string) error { @@ -133,16 +140,13 @@ func validateRootX509(product string, x *x509.Certificate, version int, role, cn return validateCRLlink(x, product, role) } -// ValidateAskX509 checks expected metadata about the ASK X.509 certificate. It does not verify the +// validateAskX509 checks expected metadata about the ASK X.509 certificate. It does not verify the // cryptographic signatures. -func ValidateAskX509(r *trust.AMDRootCerts) error { +func validateAskX509(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } - var cn string - if r.Product != "" { - cn = fmt.Sprintf("SEV-%s", r.Product) - } + cn := intermediateKeyCommonName(r.Product, abi.VcekReportSigner) if err := validateRootX509(r.Product, r.ProductCerts.Ask, askX509Version, "ASK", cn); err != nil { return err } @@ -152,9 +156,36 @@ func ValidateAskX509(r *trust.AMDRootCerts) error { return nil } +func endorsementKeyCommonName(key abi.ReportSigner) string { + return fmt.Sprintf("SEV-%v", key) +} + +func intermediateKeyCommonName(product string, key abi.ReportSigner) string { + if product != "" { + switch key { + case abi.VcekReportSigner: + return fmt.Sprintf("SEV-%s", product) + case abi.VlekReportSigner: + return fmt.Sprintf("SEV-VLEK-%s", product) + } + } + return "" +} + +// validateAsvkX509 checks expected metadata about the ASVK X.509 certificate. It does not verify the +// cryptographic signatures. +func validateAsvkX509(r *trust.AMDRootCerts) error { + if r == nil { + r = trust.DefaultRootCerts["Milan"] + } + cn := intermediateKeyCommonName(r.Product, abi.VlekReportSigner) + // There is no ASVK SEV ABI key released by AMD to cross-check. + return validateRootX509(r.Product, r.ProductCerts.Asvk, asvkX509Version, "ASVK", cn) +} + // ValidateArkX509 checks expected metadata about the ARK X.509 certificate. It does not verify the // cryptographic signatures. -func ValidateArkX509(r *trust.AMDRootCerts) error { +func validateArkX509(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } @@ -184,9 +215,9 @@ func validateRootSev(subject, issuer *abi.AskCert, version, keyUsage uint32, sub return askVerifiedBy(subject, issuer, subjectRole, issuerRole) } -// ValidateAskSev checks ASK SEV format certificate validity according to AMD SEV API Appendix B.3 +// validateAskSev checks ASK SEV format certificate validity according to AMD SEV API Appendix B.3 // This covers steps 1, 2, and 5 -func ValidateAskSev(r *trust.AMDRootCerts) error { +func validateAskSev(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } @@ -195,43 +226,57 @@ func ValidateAskSev(r *trust.AMDRootCerts) error { // ValidateArkSev checks ARK certificate validity according to AMD SEV API Appendix B.3 // This covers steps 5, 6, 9, and 11. -func ValidateArkSev(r *trust.AMDRootCerts) error { +func validateArkSev(r *trust.AMDRootCerts) error { if r == nil { r = trust.DefaultRootCerts["Milan"] } return validateRootSev(r.ArkSev, r.ArkSev, arkVersion, arkKeyUsage, "ARK", "ARK") } -// ValidateX509 will validate the x509 certificates of the ASK and ARK. -func ValidateX509(r *trust.AMDRootCerts) error { - if err := ValidateArkX509(r); err != nil { +// validateX509 will validate the x509 certificates of the ASK, ASVK, and ARK. +func validateX509(r *trust.AMDRootCerts, key abi.ReportSigner) error { + if err := validateArkX509(r); err != nil { return fmt.Errorf("ARK validation error: %v", err) } - if err := ValidateAskX509(r); err != nil { - return fmt.Errorf("ASK validation error: %v", err) + if r.ProductCerts.Ask == nil && key == abi.VcekReportSigner { + return fmt.Errorf("trusted root must have ASK certificate for VCEK chain") + } + if r.ProductCerts.Asvk == nil && key == abi.VlekReportSigner { + return fmt.Errorf("trusted root must have ASVK certificate for VLEK chain") + } + if r.ProductCerts.Ask != nil { + if err := validateAskX509(r); err != nil { + return fmt.Errorf("ASK validation error: %v", err) + } + } + if r.ProductCerts.Asvk != nil { + if err := validateAsvkX509(r); err != nil { + return fmt.Errorf("ASVK validation error: %v", err) + } } return nil } -// ValidateVcekCertSubject checks KDS-specified values of the subject metadata of the AMD certificate. -func ValidateVcekCertSubject(subject pkix.Name) error { - if err := validateAmdLocation(subject, "VCEK subject"); err != nil { +// validateKDSCertSubject checks KDS-specified values of the subject metadata of the AMD certificate. +func validateKDSCertSubject(subject pkix.Name, key abi.ReportSigner) error { + if err := validateAmdLocation(subject, fmt.Sprintf("%v subject", key)); err != nil { return err } - if subject.CommonName != "SEV-VCEK" { - return fmt.Errorf("VCEK certificate subject common name %s not expected. Expected SEV-VCEK", subject.CommonName) + want := endorsementKeyCommonName(key) + if subject.CommonName != want { + return fmt.Errorf("%s certificate subject common name %v not expected. Expected %s", key, subject.CommonName, want) } return nil } -// ValidateVcekCertIssuer checks KDS-specified values of the issuer metadata of the AMD certificate. -func ValidateVcekCertIssuer(r *trust.AMDRootCerts, issuer pkix.Name) error { - if err := validateAmdLocation(issuer, "VCEK issuer"); err != nil { +// validateKDSCertIssuer checks KDS-specified values of the issuer metadata of the AMD certificate. +func validateKDSCertIssuer(r *trust.AMDRootCerts, issuer pkix.Name, key abi.ReportSigner) error { + if err := validateAmdLocation(issuer, fmt.Sprintf("%v issuer", key)); err != nil { return err } - cn := fmt.Sprintf("SEV-%s", r.Product) + cn := intermediateKeyCommonName(r.Product, key) if issuer.CommonName != cn { - return fmt.Errorf("VCEK certificate issuer common name %s not expected. Expected %s", issuer.CommonName, cn) + return fmt.Errorf("%s certificate issuer common name %v not expected. Expected %s", key, issuer.CommonName, cn) } return nil } @@ -286,7 +331,7 @@ func verifyCRL(r *trust.AMDRootCerts) error { if r.ProductCerts.Ark == nil { return errors.New("missing ARK x509 certificate to check CRL validity") } - if r.ProductCerts.Ark == nil { + if r.ProductCerts.Ask == nil { return errors.New("missing ASK x509 certificate to check intermediate key validity") } if err := r.CRL.CheckSignatureFrom(r.ProductCerts.Ark); err != nil { @@ -309,8 +354,10 @@ func VcekNotRevoked(r *trust.AMDRootCerts, _ *x509.Certificate, options *Options return err } +// product is expected to be of form "Milan" or "Genoa". +// role is expected to be one of "ARK", "ASK", "ASVK". func validateCRLlink(x *x509.Certificate, product, role string) error { - url := fmt.Sprintf("https://kdsintf.amd.com/vcek/v1/%s/crl", product) + url := kds.CrlLinkByRole(product, role) if len(x.CRLDistributionPoints) != 1 { return fmt.Errorf("%s has %d CRL distribution points, want 1", role, len(x.CRLDistributionPoints)) } @@ -322,65 +369,77 @@ func validateCRLlink(x *x509.Certificate, product, role string) error { // validateVcekExtensions checks if the certificate extensions match // wellformedness expectations. -func validateVcekExtensions(exts *kds.VcekExtensions) error { - _, err := kds.ParseProductName(exts.ProductName) +func validateExtensions(exts *kds.Extensions, key abi.ReportSigner) error { + _, err := kds.ParseProductName(exts.ProductName, key) return err } -// validateVcekCertificateProductNonspecific returns an error if the given certificate doesn't have -// the documented qualities of a VCEK certificate according to Key Distribution Service +// validateKDSCertificateProductNonspecific returns an error if the given certificate doesn't have +// the documented qualities of a V[CL]EK certificate according to Key Distribution Service // documentation: // https://www.amd.com/system/files/TechDocs/57230.pdf // This does not check the certificate revocation list since that requires internet access. -// If valid, then returns the VCEK-specific certificate extensions in the VcekExtensions type. -func validateVcekCertificateProductNonspecific(cert *x509.Certificate) (*kds.VcekExtensions, error) { +// If valid, then returns the V[CL]EK-specific certificate extensions in the VcekExtensions type. +func validateKDSCertificateProductNonspecific(cert *x509.Certificate, key abi.ReportSigner) (*kds.Extensions, error) { if cert.Version != 3 { - return nil, fmt.Errorf("VCEK certificate version is %v, expected 3", cert.Version) + return nil, fmt.Errorf("%v certificate version is %v, expected 3", key, cert.Version) } // Signature algorithm: RSASSA-PSS // Signature hash algorithm sha384 if cert.SignatureAlgorithm != x509.SHA384WithRSAPSS { - return nil, fmt.Errorf("VCEK certificate signature algorithm is %v, expected SHA-384 with RSASSA-PSS", cert.SignatureAlgorithm) + return nil, fmt.Errorf("%v certificate signature algorithm is %v, expected SHA-384 with RSASSA-PSS", key, cert.SignatureAlgorithm) } // Subject Public Key Info ECDSA on curve P-384 if cert.PublicKeyAlgorithm != x509.ECDSA { - return nil, fmt.Errorf("VCEK certificate public key type is %v, expected ECDSA", cert.PublicKeyAlgorithm) + return nil, fmt.Errorf("%v certificate public key type is %v, expected ECDSA", key, cert.PublicKeyAlgorithm) } // Locally bind the public key any type to allow for occurrence typing in the switch statement. switch pub := cert.PublicKey.(type) { case *ecdsa.PublicKey: if pub.Curve.Params().Name != "P-384" { - return nil, fmt.Errorf("VCEK certificate public key curve is %s, expected P-384", pub.Curve.Params().Name) + return nil, fmt.Errorf("%v certificate public key curve is %s, expected P-384", key, pub.Curve.Params().Name) } default: - return nil, fmt.Errorf("VCEK certificate public key not ecdsa PublicKey type %v", pub) + return nil, fmt.Errorf("%v certificate public key not ecdsa PublicKey type %v", key, pub) } - if err := ValidateVcekCertSubject(cert.Subject); err != nil { + if err := validateKDSCertSubject(cert.Subject, key); err != nil { return nil, err } - exts, err := kds.VcekCertificateExtensions(cert) + exts, err := kds.CertificateExtensions(cert, key) if err != nil { return nil, err } - if err := validateVcekExtensions(exts); err != nil { + if err := validateExtensions(exts, key); err != nil { return nil, err } return exts, nil } -func validateVcekCertificateProductSpecifics(r *trust.AMDRootCerts, cert *x509.Certificate, opts *Options) error { - if err := ValidateVcekCertIssuer(r, cert.Issuer); err != nil { +func validateKDSCertificateProductSpecifics(r *trust.AMDRootCerts, cert *x509.Certificate, key abi.ReportSigner, opts *Options) error { + if err := validateKDSCertIssuer(r, cert.Issuer, key); err != nil { return err } - if _, err := cert.Verify(*r.X509Options(opts.Now)); err != nil { - return fmt.Errorf("error verifying VCEK certificate: %v (%v)", err, r.ProductCerts.Ask.IsCA) + // ica: Intermediate Certificate Authority. + ica := r.ProductCerts.Ask + if key == abi.VlekReportSigner { + ica = r.ProductCerts.Asvk + } + if ica == nil { + return fmt.Errorf("root of trust missing intermediate certificate authority certificate for key %v", key) + } + verifyOpts := r.X509Options(opts.Now, key) + if verifyOpts == nil { + return fmt.Errorf("internal error: could not get X509 options for %v (missing ARK cert or ICA cert)", key) + } + if _, err := cert.Verify(*verifyOpts); err != nil { + return fmt.Errorf("error verifying %v certificate: %v (%v)", key, err, ica.IsCA) } // VCEK is not expected to have a CRL link. return nil } -func checkProductName(got, want *spb.SevProduct) error { +func checkProductName(got, want *spb.SevProduct, key abi.ReportSigner) error { // No constraint if want == nil { return nil @@ -389,35 +448,44 @@ func checkProductName(got, want *spb.SevProduct) error { return fmt.Errorf("internal error: no product name") } if got.Name != want.Name { - return fmt.Errorf("VCEK cert product name %v is not %v", got, want) + return fmt.Errorf("%v cert product name %v is not %v", key, got, want) } - if got.ModelStepping != want.ModelStepping { - return fmt.Errorf("VCEK cert product model-stepping number %02X is not %02X", got.ModelStepping, want.ModelStepping) + // The model stepping number is only part of the VLEK product name, not VLEK's. + if key == abi.VcekReportSigner && got.ModelStepping != want.ModelStepping { + return fmt.Errorf("%v cert product model-stepping number %02X is not %02X", + key, got.ModelStepping, want.ModelStepping) } return nil } -// decodeCerts checks that the VCEK certificate matches expected fields +// decodeCerts checks that the V[CL]EK certificate matches expected fields // from the KDS specification and also that its certificate chain matches // hardcoded trusted root certificates from AMD. -func decodeCerts(vcek []byte, ask []byte, ark []byte, options *Options) (*x509.Certificate, *trust.AMDRootCerts, error) { - vcekCert, err := trust.ParseCert(vcek) +func decodeCerts(chain *spb.CertificateChain, key abi.ReportSigner, options *Options) (*x509.Certificate, *trust.AMDRootCerts, error) { + var ek []byte + switch key { + case abi.VcekReportSigner: + ek = chain.GetVcekCert() + case abi.VlekReportSigner: + ek = chain.GetVlekCert() + } + endorsementKeyCert, err := trust.ParseCert(ek) if err != nil { - return nil, nil, fmt.Errorf("could not interpret VCEK DER bytes: %v", err) + return nil, nil, fmt.Errorf("could not interpret %v DER bytes: %v", key, err) } - exts, err := validateVcekCertificateProductNonspecific(vcekCert) + exts, err := validateKDSCertificateProductNonspecific(endorsementKeyCert, key) if err != nil { return nil, nil, err } roots := options.TrustedRoots - product, err := kds.ParseProductName(exts.ProductName) + product, err := kds.ParseProductName(exts.ProductName, key) if err != nil { return nil, nil, err } productName := kds.ProductString(product) // Ensure the extension product info matches expectations. - if err := checkProductName(product, options.Product); err != nil { + if err := checkProductName(product, options.Product, key); err != nil { return nil, nil, err } if len(roots) == 0 { @@ -428,10 +496,10 @@ func decodeCerts(vcek []byte, ask []byte, ark []byte, options *Options) (*x509.C AskSev: trust.DefaultRootCerts[productName].AskSev, ArkSev: trust.DefaultRootCerts[productName].ArkSev, } - if err := root.Decode(ask, ark); err != nil { + if err := root.Decode(chain.GetAskCert(), chain.GetArkCert()); err != nil { return nil, nil, err } - if err := ValidateX509(root); err != nil { + if err := validateX509(root, key); err != nil { return nil, nil, err } roots = map[string][]*trust.AMDRootCerts{ @@ -440,13 +508,13 @@ func decodeCerts(vcek []byte, ask []byte, ark []byte, options *Options) (*x509.C } var lastErr error for _, productRoot := range roots[productName] { - if err := validateVcekCertificateProductSpecifics(productRoot, vcekCert, options); err != nil { + if err := validateKDSCertificateProductSpecifics(productRoot, endorsementKeyCert, key, options); err != nil { lastErr = err continue } - return vcekCert, productRoot, nil + return endorsementKeyCert, productRoot, nil } - return nil, nil, fmt.Errorf("VCEK could not be verified by any trusted roots. Last error: %v", lastErr) + return nil, nil, fmt.Errorf("%v could not be verified by any trusted roots. Last error: %v", key, lastErr) } // SnpReportSignature verifies the attestation report's signature based on the report's @@ -561,17 +629,22 @@ func SnpAttestation(attestation *spb.Attestation, options *Options) error { // that this is a noop if options.Product began as non-nil. options.Product = attestation.Product + report := attestation.GetReport() + info, err := abi.ParseSignerInfo(report.GetSignerInfo()) + if err != nil { + return err + } chain := attestation.GetCertificateChain() - vcek, root, err := decodeCerts(chain.GetVcekCert(), chain.GetAskCert(), chain.GetArkCert(), options) + endorsementKeyCert, root, err := decodeCerts(chain, info.SigningKey, options) if err != nil { return err } if options != nil && options.CheckRevocations { - if err := VcekNotRevoked(root, vcek, options); err != nil { + if err := VcekNotRevoked(root, endorsementKeyCert, options); err != nil { return err } } - return SnpProtoReportSignature(attestation.GetReport(), vcek) + return SnpProtoReportSignature(report, endorsementKeyCert) } // fillInAttestation uses AMD's KDS to populate any empty certificate field in the attestation's @@ -596,13 +669,17 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { getter = trust.DefaultHTTPSGetter() } report := attestation.GetReport() + info, err := abi.ParseSignerInfo(report.GetSignerInfo()) + if err != nil { + return err + } chain := attestation.GetCertificateChain() if chain == nil { chain = &spb.CertificateChain{} attestation.CertificateChain = chain } if len(chain.GetAskCert()) == 0 || len(chain.GetArkCert()) == 0 { - askark, err := trust.GetProductChain(product, getter) + askark, err := trust.GetProductChain(product, info.SigningKey, getter) if err != nil { return err } @@ -614,15 +691,24 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { chain.ArkCert = askark.Ark.Raw } } - if len(chain.GetVcekCert()) == 0 { - vcekURL := kds.VCEKCertURL(product, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb())) - vcek, err := getter.Get(vcekURL) - if err != nil { - return &trust.AttestationRecreationErr{ - Msg: fmt.Sprintf("could not download VCEK certificate: %v", err), + switch info.SigningKey { + case abi.VcekReportSigner: + if len(chain.GetVcekCert()) == 0 { + vcekURL := kds.VCEKCertURL(product, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb())) + vcek, err := getter.Get(vcekURL) + if err != nil { + return &trust.AttestationRecreationErr{ + Msg: fmt.Sprintf("could not download VCEK certificate: %v", err), + } } + chain.VcekCert = vcek + } + case abi.VlekReportSigner: + // We can't lazily ask KDS for the certificate as a user. The CSP must cache their provisioned + // certificates and provide them in GET_EXT_REPORT. + if len(chain.GetVlekCert()) == 0 { + return ErrMissingVlek } - chain.VcekCert = vcek } return nil } diff --git a/verify/verify_test.go b/verify/verify_test.go index 1821f47..6b7a1cf 100644 --- a/verify/verify_test.go +++ b/verify/verify_test.go @@ -47,7 +47,7 @@ var ( ) func initSigner() { - newSigner, err := test.DefaultCertChain(product, time.Now()) + newSigner, err := test.DefaultTestOnlyCertChain(product, time.Now()) if err != nil { // Unexpected panic(err) } @@ -63,10 +63,10 @@ func TestEmbeddedCertsAppendixB3Expectations(t *testing.T) { // https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf // Appendix B.1 for _, root := range trust.DefaultRootCerts { - if err := ValidateAskSev(root); err != nil { + if err := validateAskSev(root); err != nil { t.Errorf("Embedded ASK failed validation: %v", err) } - if err := ValidateArkSev(root); err != nil { + if err := validateArkSev(root); err != nil { t.Errorf("Embedded ARK failed validation: %v", err) } } @@ -83,10 +83,10 @@ func TestFakeCertsKDSExpectations(t *testing.T) { }, // No ArkSev or AskSev intentionally for test certs. } - if err := ValidateArkX509(root); err != nil { + if err := validateArkX509(root); err != nil { t.Errorf("fake ARK validation error: %v", err) } - if err := ValidateAskX509(root); err != nil { + if err := validateAskX509(root); err != nil { t.Errorf("fake ASK validation error: %v", err) } } @@ -96,7 +96,7 @@ func TestParseVcekCert(t *testing.T) { if err != nil { t.Errorf("could not parse valid VCEK certificate: %v", err) } - if _, err := validateVcekCertificateProductNonspecific(cert); err != nil { + if _, err := validateKDSCertificateProductNonspecific(cert, abi.VcekReportSigner); err != nil { t.Errorf("could not validate valid VCEK certificate: %v", err) } } @@ -105,7 +105,7 @@ func TestVerifyVcekCert(t *testing.T) { // This certificate is committed regardless of its expiration date, but we'll adjust the // CurrentTime to compare against so that the validity with respect to time is always true. root := new(trust.AMDRootCerts) - if err := root.FromKDSCertBytes(testdata.MilanBytes); err != nil { + if err := root.FromKDSCertBytes(testdata.MilanVcekBytes); err != nil { t.Fatalf("could not read Milan certificate file: %v", err) } vcek, err := x509.ParseCertificate(testdata.VcekBytes) @@ -113,9 +113,10 @@ func TestVerifyVcekCert(t *testing.T) { t.Errorf("could not parse valid VCEK certificate: %v", err) } now := time.Date(2022, time.September, 24, 1, 0, 0, 0, time.UTC) - opts := root.X509Options(now) + opts := root.X509Options(now, abi.VcekReportSigner) if opts == nil { t.Fatalf("root x509 certificates missing: %v", root) + return } // This time is within the 25 year lifespan of the Milan product. chains, err := vcek.Verify(*opts) @@ -165,7 +166,11 @@ func TestSnpReportSignature(t *testing.T) { if !bytes.Equal(got, want) { t.Errorf("%s: GetRawReport(%v) = %v, want %v", tc.Name, tc.Input, got, want) } - if err := SnpReportSignature(raw, d.Signer.Vcek); err != nil { + key := d.Signer.Vcek + if tc.EK == test.KeyChoiceVlek { + key = d.Signer.Vlek + } + if err := SnpReportSignature(raw, key); err != nil { t.Errorf("signature with test keys did not verify: %v", err) } } @@ -291,9 +296,9 @@ func TestKdsMetadataLogic(t *testing.T) { } for _, tc := range tests { bcopy := tc.builder - newSigner, err := (&bcopy).CertChain() + newSigner, err := (&bcopy).TestOnlyCertChain() if err != nil { - t.Errorf("%+v.CertChain() errored unexpectedly: %v", tc.builder, err) + t.Errorf("%+v.TestOnlyCertChain() errored unexpectedly: %v", tc.builder, err) continue } // Trust the test-generated root if the test should pass. Otherwise, other root logic @@ -314,7 +319,7 @@ func TestKdsMetadataLogic(t *testing.T) { options = &Options{} } vcekPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: newSigner.Vcek.Raw}) - vcek, _, err := decodeCerts(vcekPem, newSigner.Ask.Raw, newSigner.Ark.Raw, options) + vcek, _, err := decodeCerts(&pb.CertificateChain{VcekCert: vcekPem, AskCert: newSigner.Ask.Raw, ArkCert: newSigner.Ark.Raw}, abi.VcekReportSigner, options) if !test.Match(err, tc.wantErr) { t.Errorf("%s: decodeCerts(...) = %+v, %v did not error as expected. Want %q", tc.name, vcek, err, tc.wantErr) } @@ -336,10 +341,13 @@ func TestCRLRootValidity(t *testing.T) { ArkCreationTime: now, AskCreationTime: now, VcekCreationTime: now, + CSPID: "go-sev-guest", Keys: &test.AmdKeys{ Ark: ark2, Ask: signer.Keys.Ask, + Asvk: signer.Keys.Asvk, Vcek: signer.Keys.Vcek, + Vlek: signer.Keys.Vlek, }, VcekCustom: test.CertOverride{ SerialNumber: big.NewInt(0xd), @@ -348,7 +356,7 @@ func TestCRLRootValidity(t *testing.T) { SerialNumber: big.NewInt(0x8088), }, } - signer2, err := sb.CertChain() + signer2, err := sb.TestOnlyCertChain() if err != nil { t.Fatal(err) } @@ -407,47 +415,104 @@ func TestOpenGetExtendedReportVerifyClose(t *testing.T) { d, goodRoots, badRoots, kds := testclient.GetSevGuest(tests, &test.DeviceOptions{Now: time.Now()}, t) defer d.Close() type reportGetter func(sg.Device, [64]byte) (*pb.Attestation, error) + reportOnly := func(d sg.Device, input [64]byte) (*pb.Attestation, error) { + report, err := sg.GetReport(d, input) + if err != nil { + return nil, err + } + return &pb.Attestation{Report: report}, nil + } reportGetters := []struct { - name string - getter reportGetter + name string + getter reportGetter + skipVlek bool + badRootErr string + vlekOnly bool + vlekErr string + vlekBadRootErr string }{ { - name: "GetExtendedReport", - getter: sg.GetExtendedReport, + name: "GetExtendedReport", + getter: sg.GetExtendedReport, + badRootErr: "error verifying VCEK certificate", + vlekBadRootErr: "error verifying VLEK certificate", }, { - name: "GetReport", + name: "GetReport", + getter: reportOnly, + badRootErr: "error verifying VCEK certificate", + vlekErr: "VLEK certificate is missing", + vlekBadRootErr: "VLEK certificate is missing", + }, + { + name: "GetReportVlek", getter: func(d sg.Device, input [64]byte) (*pb.Attestation, error) { - report, err := sg.GetReport(d, input) + attestation, err := reportOnly(d, input) if err != nil { return nil, err } - return &pb.Attestation{Report: report}, nil + // If fake, we can provide the VLEK. Otherwise we have to error. + if attestation.CertificateChain == nil { + attestation.CertificateChain = &pb.CertificateChain{} + } + chain := attestation.CertificateChain + info, _ := abi.ParseSignerInfo(attestation.GetReport().GetSignerInfo()) + if sg.UseDefaultSevGuest() && info.SigningKey == abi.VlekReportSigner { + if td, ok := d.(*test.Device); ok { + chain.VlekCert = td.Signer.Vlek.Raw + } + } + return attestation, nil }, + skipVlek: !sg.UseDefaultSevGuest(), + vlekOnly: true, + badRootErr: "error verifying VLEK certificate", + vlekBadRootErr: "error verifying VLEK certificate", }, } // Trust the test device's root certs. options := &Options{TrustedRoots: goodRoots, Getter: kds} badOptions := &Options{TrustedRoots: badRoots, Getter: kds} for _, tc := range tests { - if testclient.SkipUnmockableTestCase(&tc) { - continue - } - for _, getReport := range reportGetters { - ereport, err := getReport.getter(d, tc.Input) - if !test.Match(err, tc.WantErr) { - t.Fatalf("%s: %s(d, %v) = %v, %v. Want err: %v", tc.Name, getReport.name, tc.Input, ereport, err, tc.WantErr) + t.Run(tc.Name, func(t *testing.T) { + if testclient.SkipUnmockableTestCase(&tc) { + t.Skip() + return } - if tc.WantErr == "" { - if err := SnpAttestation(ereport, options); err != nil { - t.Errorf("SnpAttestation(%v) errored unexpectedly: %v", ereport, err) + + for _, getReport := range reportGetters { + if getReport.skipVlek && tc.EK == test.KeyChoiceVlek { + t.Skip() + continue } - wantBad := "error verifying VCEK certificate" - if err := SnpAttestation(ereport, badOptions); !test.Match(err, wantBad) { - t.Errorf("SnpAttestation(_) bad root test errored unexpectedly: %v, want %s", err, wantBad) + if getReport.vlekOnly && tc.EK != test.KeyChoiceVlek { + t.Skip() + continue + } + ereport, err := getReport.getter(d, tc.Input) + if !test.Match(err, tc.WantErr) { + t.Fatalf("%s: %s(d, %v) = %v, %v. Want err: %v", tc.Name, getReport.name, tc.Input, ereport, err, tc.WantErr) + } + if tc.WantErr == "" { + var wantAttestationErr string + if tc.EK == test.KeyChoiceVlek && getReport.vlekErr != "" { + wantAttestationErr = getReport.vlekErr + } + if err := SnpAttestation(ereport, options); !test.Match(err, wantAttestationErr) { + t.Errorf("SnpAttestation(%v) = %v. Want err: %q", ereport, err, wantAttestationErr) + } + + wantBad := getReport.badRootErr + if tc.EK == test.KeyChoiceVlek && getReport.vlekBadRootErr != "" { + wantBad = getReport.vlekBadRootErr + } + if err := SnpAttestation(ereport, badOptions); !test.Match(err, wantBad) { + t.Errorf("%s: SnpAttestation(_) bad root test errored unexpectedly: %v, want %s", + getReport.name, err, wantBad) + } } } - } + }) } } @@ -457,7 +522,7 @@ func TestRealAttestationVerification(t *testing.T) { copy(nonce[:], []byte{1, 2, 3, 4, 5}) getter := test.SimpleGetter( map[string][]byte{ - "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain": testdata.MilanBytes, + "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain": testdata.MilanVcekBytes, // Use the VCEK's hwID and known TCB values to specify the URL its VCEK cert would be fetched from. "https://kdsintf.amd.com/vcek/v1/Milan/3ac3fe21e13fb0990eb28a802e3fb6a29483a6b0753590c951bdd3b8e53786184ca39e359669a2b76a1936776b564ea464cdce40c05f63c9b610c5068b006b5d?blSPL=2&teeSPL=0&snpSPL=5&ucodeSPL=68": testdata.VcekBytes, },