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..d6014fe 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,16 @@ func makeTestCerts(opts *DeviceOptions) ([]byte, *AmdSigner, error) { return certs, signer, nil } +// KeyChoice represents which key is expected to have signed the report. +type KeyChoice int + +const ( + // KeyChoiceVcek represents the default choice of the VCEK signing the report. + KeyChoiceVcek = iota + // KeyChoiceVlek represents the choice of the VLEK signing the report. + KeyChoiceVlek +) + // TestCase represents a get_report input/output test case. type TestCase struct { Name string @@ -134,6 +189,7 @@ type TestCase struct { OutputProto string FwErr abi.SevFirmwareStatus EsResult labi.EsResult + EK KeyChoice WantErr string } @@ -141,6 +197,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 +214,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, },