From 819b39ac3dbe46c621fd585532918743cd7ee211 Mon Sep 17 00:00:00 2001 From: Dionna Glaze Date: Mon, 12 Feb 2024 18:16:43 +0000 Subject: [PATCH] Add SevProduct raw cert representation. This is to help Issue#108. The RawQuote interface is missing SevProduct that is present in the Attestation message, so use a new GUID entry in the certificate table to add custom information that can be forwarded to the verifier. --- abi/abi.go | 139 +++++++++++++++++++++++++++++--- abi/abi_test.go | 100 ++++++++++++++++++++--- client/client.go | 5 ++ client/client_linux.go | 25 +++++- testing/mocks.go | 12 ++- testing/test_cases.go | 8 +- tools/check/check_test.go | 14 ++-- tools/lib/report/report_test.go | 51 ++++++------ validate/validate_test.go | 7 +- verify/verify.go | 54 ++++++++++--- verify/verify_test.go | 4 +- 11 files changed, 346 insertions(+), 73 deletions(-) diff --git a/abi/abi.go b/abi/abi.go index c201742..da97fe0 100644 --- a/abi/abi.go +++ b/abi/abi.go @@ -110,6 +110,22 @@ const ( // a single machine can use both VCEK and VLEK report signing. AsvkGUID = "00000000-0000-0000-0000-000000000000" + // ExtraPlatformInfoGUID represents more information about the machine collecting an attestation + // report than just the report to help interpret the attestation report. + ExtraPlatformInfoGUID = "ecae0c0f-9502-43b1-afa2-0ae2e0d565b6" + // ExtraPlatformInfoV0Size is the minimum size for an ExtraPlatformInfo blob. + ExtraPlatformInfoV0Size = 8 + + // CpuidProductMask keeps only the SevProduct-relevant bits from the CPUID(1).EAX result. + CpuidProductMask = 0x0fff0f0f + extendedFamilyShift = 20 + extendedModelShift = 16 + familyShift = 8 + sevExtendedFamily = 0xA + sevFamily = 0xF + milanExtendedModel = 0 + genoaExtendedModel = 1 + // ExpectedReportVersion is set by the SNP API specification // https://www.amd.com/system/files/TechDocs/56860.pdf ExpectedReportVersion = 2 @@ -874,34 +890,31 @@ func (c *CertTable) Proto() *pb.CertificateChain { // See assembly implementations in cpuid_*.s var cpuid func(op uint32) (eax, ebx, ecx, edx uint32) -// SevProduct returns the SEV product enum for the CPU that runs this -// function. Ought to be called from the client, not the verifier. -func SevProduct() *pb.SevProduct { - // CPUID[EAX=1] is the processor info. The only bits we care about are in - // the eax result. - eax, _, _, _ := cpuid(1) +// SevProductFromCpuid1Eax returns the SevProduct that is represented by cpuid(1).eax. +func SevProductFromCpuid1Eax(eax uint32) *pb.SevProduct { // 31:28 reserved // 27:20 Extended Family ID - extendedFamily := (eax >> 20) & 0xff + extendedFamily := (eax >> extendedFamilyShift) & 0xff // 19:16 Extended Model ID - extendedModel := (eax >> 16) & 0xf + extendedModel := (eax >> extendedModelShift) & 0xf // 15:14 reserved // 11:8 Family ID - family := (eax >> 8) & 0xf + family := (eax >> familyShift) & 0xf // 3:0 Stepping stepping := eax & 0xf // Ah, Fh, {0h,1h} values from the KDS specification, // section "Determining the Product Name". var productName pb.SevProduct_SevProductName // Product information specified by processor programming reference publications. - if extendedFamily == 0xA && family == 0xF { + if extendedFamily == sevExtendedFamily && family == sevFamily { switch extendedModel { - case 0: + case milanExtendedModel: productName = pb.SevProduct_SEV_PRODUCT_MILAN - case 1: + case genoaExtendedModel: productName = pb.SevProduct_SEV_PRODUCT_GENOA default: productName = pb.SevProduct_SEV_PRODUCT_UNKNOWN + stepping = 0 // Reveal nothing. } } return &pb.SevProduct{ @@ -910,6 +923,47 @@ func SevProduct() *pb.SevProduct { } } +// MaskedCpuid1EaxFromSevProduct returns the Cpuid1Eax value expected from the given product +// when masked with CpuidProductMask. +func MaskedCpuid1EaxFromSevProduct(product *pb.SevProduct) uint32 { + var stepping uint32 + if product.MachineStepping != nil { + stepping = product.MachineStepping.Value & 0xf + } + extendedFamily := uint32(sevExtendedFamily) << extendedFamilyShift + family := uint32(sevFamily) << familyShift + + var extendedModel uint32 + switch product.Name { + case pb.SevProduct_SEV_PRODUCT_MILAN: + extendedModel = milanExtendedModel + case pb.SevProduct_SEV_PRODUCT_GENOA: + extendedModel = genoaExtendedModel + default: + return 0 + } + return extendedFamily | family | stepping | (extendedModel << extendedModelShift) +} + +// SevProduct returns the SEV product enum for the CPU that runs this +// function. Ought to be called from the client, not the verifier. +func SevProduct() *pb.SevProduct { + // CPUID[EAX=1] is the processor info. The only bits we care about are in + // the eax result. + eax, _, _, _ := cpuid(1) + return SevProductFromCpuid1Eax(eax & CpuidProductMask) +} + +// MakeExtraPlatformInfo returns the representation of platform info needed on top of what an +// attestation report provides in order to interpret it with the help of the AMD KDS. +func MakeExtraPlatformInfo() *ExtraPlatformInfo { + eax, _, _, _ := cpuid(1) + return &ExtraPlatformInfo{ + Size: ExtraPlatformInfoV0Size, + Cpuid1Eax: eax & CpuidProductMask, + } +} + // DefaultSevProduct returns the initial product version for a commercially available AMD SEV-SNP chip. func DefaultSevProduct() *pb.SevProduct { return &pb.SevProduct{ @@ -917,3 +971,64 @@ func DefaultSevProduct() *pb.SevProduct { MachineStepping: &wrapperspb.UInt32Value{Value: 1}, } } + +// ExtraPlatformInfo represents environment information needed to interpret an attestation report when +// the VCEK certificate is not available in the auxblob. +type ExtraPlatformInfo struct { + Size uint32 // Size doubles as Version, following the Linux ABI expansion methodology. + Cpuid1Eax uint32 // Provides product information +} + +// ParseExtraPlatformInfo extracts an ExtraPlatformInfo from a blob if it matches expectations, or +// errors. +func ParseExtraPlatformInfo(data []byte) (*ExtraPlatformInfo, error) { + if len(data) < ExtraPlatformInfoV0Size { + return nil, fmt.Errorf("%d bytes is too small for ExtraPlatformInfoSize. Want >= %d bytes", + len(data), ExtraPlatformInfoV0Size) + } + // Populate V0 data. + result := &ExtraPlatformInfo{ + Size: binary.LittleEndian.Uint32(data[0:0x04]), + Cpuid1Eax: binary.LittleEndian.Uint32(data[0x04:0x08]), + } + if uint32(len(data)) != result.Size { + return nil, fmt.Errorf("actual size %d bytes != reported size %d bytes", len(data), result.Size) + } + return result, nil +} + +// Marshal returns ExtraPlatformInfo in its ABI format or errors. +func (i *ExtraPlatformInfo) Marshal() ([]byte, error) { + if i.Size != ExtraPlatformInfoV0Size { + return nil, fmt.Errorf("unsupported ExtraPlatformInfo size %d bytes", i.Size) + } + data := make([]byte, ExtraPlatformInfoV0Size) + binary.LittleEndian.PutUint32(data[0:0x04], i.Size) + binary.LittleEndian.PutUint32(data[0x04:0x08], i.Cpuid1Eax) + return data, nil +} + +// ExtendPlatformCertTable is a convenience function for parsing a CertTable, adding the +// ExtraPlatformInfoGUID entry, and returning the marshaled extended table. +func ExtendPlatformCertTable(data []byte, info *ExtraPlatformInfo) ([]byte, error) { + certs := new(CertTable) + if err := certs.Unmarshal(data); err != nil { + return nil, err + } + // A directly constructed info cannot have a marshaling error. + extra, err := info.Marshal() + if err != nil { + return nil, fmt.Errorf("could not marshal ExtraPlatformInfo: %v", err) + } + certs.Entries = append(certs.Entries, CertTableEntry{ + GUID: uuid.Parse(ExtraPlatformInfoGUID), + RawCert: extra, + }) + return certs.Marshal(), nil +} + +// ExtendedPlatformCertTable is a convenience function for parsing a CertTable, adding the +// ExtraPlatformInfoGUID entry, and returning the marshaled extended table. +func ExtendedPlatformCertTable(data []byte) ([]byte, error) { + return ExtendPlatformCertTable(data, MakeExtraPlatformInfo()) +} diff --git a/abi/abi_test.go b/abi/abi_test.go index b4585b6..3c10f00 100644 --- a/abi/abi_test.go +++ b/abi/abi_test.go @@ -222,7 +222,15 @@ func TestCpuid(t *testing.T) { } } -func TestCertTableProto(t *testing.T) { +type testCertTable struct { + table []byte + extraraw []byte +} + +const extraGUID = "00000000-0000-c0de-0000-000000000000" + +func testRawCertTable(t testing.TB) *testCertTable { + t.Helper() headers := make([]CertTableHeaderEntry, 6) // ARK, ASK, VCEK, VLEK, extra, NULL arkraw := []byte("ark") askraw := []byte("ask") @@ -245,33 +253,40 @@ func TestCertTableProto(t *testing.T) { headers[3].Offset = headers[2].Offset + headers[2].Length headers[3].Length = uint32(len(vlekraw)) - extraGUID := "00000000-0000-c0de-0000-000000000000" headers[4].GUID = uuid.Parse(extraGUID) headers[4].Offset = headers[3].Offset + headers[3].Length headers[4].Length = uint32(len(extraraw)) - result := make([]byte, headers[4].Offset+headers[4].Length) + result := &testCertTable{ + table: make([]byte, headers[4].Offset+headers[4].Length), + extraraw: extraraw, + } for i, cert := range [][]byte{arkraw, askraw, vcekraw, vlekraw, extraraw} { - if err := (&headers[i]).Write(result[i*CertTableEntrySize:]); err != nil { + if err := (&headers[i]).Write(result.table[i*CertTableEntrySize:]); err != nil { t.Fatalf("could not write header %d: %v", i, err) } - copy(result[headers[i].Offset:], cert) + copy(result.table[headers[i].Offset:], cert) } + return result +} + +func TestCertTableProto(t *testing.T) { + result := testRawCertTable(t) c := new(CertTable) - if err := c.Unmarshal(result); err != nil { - t.Errorf("c.Unmarshal(%s) = %v, want nil", hex.Dump(result), err) + if err := c.Unmarshal(result.table); err != nil { + t.Errorf("c.Unmarshal(%s) = %v, want nil", hex.Dump(result.table), err) } p := c.Proto() if len(p.Extras) != 1 { t.Fatalf("got cert table Extras length %d, want 1", len(p.Extras)) } gotExtra, ok := p.Extras[extraGUID] - if !ok || !bytes.Equal(gotExtra, extraraw) { - t.Fatalf("Extras[%q] = %v, want %v", extraGUID, gotExtra, extraraw) + if !ok || !bytes.Equal(gotExtra, result.extraraw) { + t.Fatalf("Extras[%q] = %v, want %v", extraGUID, gotExtra, result.extraraw) } bs := c.Marshal() - if !bytes.Equal(bs, result) { - t.Errorf("c.Marshal() = %v, want %v", bs, result) + if !bytes.Equal(bs, result.table) { + t.Errorf("c.Marshal() = %v, want %v", bs, result.table) } } @@ -314,10 +329,71 @@ func TestSevProduct(t *testing.T) { }, } for _, tc := range tcs { - cpuid = func(op uint32) (uint32, uint32, uint32, uint32) { return tc.eax, 0, 0, 0 } + cpuid = func(uint32) (uint32, uint32, uint32, uint32) { return tc.eax, 0, 0, 0 } got := SevProduct() if diff := cmp.Diff(got, tc.want, protocmp.Transform()); diff != "" { t.Errorf("SevProduct() = %+v, want %+v. Diff: %s", got, tc.want, diff) } + got2 := SevProductFromCpuid1Eax(tc.eax) + if diff := cmp.Diff(got2, got, protocmp.Transform()); diff != "" { + t.Errorf("SevProductFromCpuid1Eax(0x%x) = %+v, want %+v. Diff: %s", tc.eax, got2, tc.want, diff) + } + } +} + +func TestExtendedPlatformCertTable(t *testing.T) { + oldCpuid := cpuid + defer func() { cpuid = oldCpuid }() + table := testRawCertTable(t).table + oldt := new(CertTable) + _ = oldt.Unmarshal(table) + pold := oldt.Proto() + + tcs := []struct { + name string + pname spb.SevProduct_SevProductName + eax uint32 + stepping uint32 + }{ + {name: "Genoa-B2 cruft", pname: spb.SevProduct_SEV_PRODUCT_GENOA, eax: 0x00a10f12, stepping: 2}, + {name: "Milan-B1 cruft", pname: spb.SevProduct_SEV_PRODUCT_MILAN, eax: 0x00a00f11, stepping: 1}, + {name: "Milan-B0", pname: spb.SevProduct_SEV_PRODUCT_MILAN, eax: 0x00a00f00, stepping: 0}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + cpuid = func(uint32) (uint32, uint32, uint32, uint32) { return tc.eax, 0, 0, 0 } + nextTable, err := ExtendedPlatformCertTable(table) + if err != nil { + t.Fatalf("ExtendedPlatformCertTable(%v) =_, %v. Want nil", table, err) + } + + newt := new(CertTable) + if err := newt.Unmarshal(nextTable); err != nil { + t.Fatalf("ExtendedPlatformCertTable(_) _ %v, which could not be unmarshaled: %v", nextTable, err) + } + pnew := newt.Proto() + if len(pnew.Extras) != len(pold.Extras)+1 { + t.Fatalf("ExtendedPlatformCertTable(_) table extras size is %d, want %d", len(pnew.Extras), len(pold.Extras)+1) + } + blob, ok := pnew.Extras[ExtraPlatformInfoGUID] + if !ok { + t.Fatalf("ExtendedPlatfromCertTable(_) table %v extras missing ExtraPlatformInfoGUID", pnew) + } + info, err := ParseExtraPlatformInfo(blob) + if err != nil { + t.Fatalf("ParseExtraPlatformInfo(%v) = _, %v. Want nil", blob, err) + } + if info.Size != ExtraPlatformInfoV0Size { + t.Errorf("ExtraPltaformInfo Size %d is not %d", info.Size, ExtraPlatformInfoV0Size) + } + if info.Cpuid1Eax != tc.eax&CpuidProductMask { + t.Errorf("ExtraPlatformInfo Cpuid1Eax 0x%x is not 0x%x", info.Cpuid1Eax, tc.eax&CpuidProductMask) + } + got := SevProductFromCpuid1Eax(info.Cpuid1Eax) + want := &spb.SevProduct{Name: tc.pname, MachineStepping: &wrapperspb.UInt32Value{Value: tc.stepping}} + if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" { + t.Errorf("ExtraPlatformInfo Cpuid1Eax product %v is not %v: %s", got, want, diff) + } + }) } } diff --git a/client/client.go b/client/client.go index 7ced490..cca0fca 100644 --- a/client/client.go +++ b/client/client.go @@ -47,6 +47,8 @@ type LeveledQuoteProvider interface { // GetRawQuote returns a raw report with the given privilege level. GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error) // Product returns AMD SEV-related CPU information of the calling CPU. + // + // Deprecated: Use abi.ExtraPlatformInfoGUID in raw quote certificate table. Product() *pb.SevProduct } @@ -57,6 +59,8 @@ type QuoteProvider interface { // GetRawQuote returns a raw report with the default privilege level. GetRawQuote(reportData [64]byte) ([]uint8, error) // Product returns AMD SEV-related CPU information of the calling CPU. + // + // Deprecated: Use abi.ExtraPlatformInfoGUID in the raw quote certificate table. Product() *pb.SevProduct } @@ -203,6 +207,7 @@ func GetQuoteProto(qp QuoteProvider, reportData [64]byte) (*pb.Attestation, erro if err != nil { return nil, err } + // TODO(Issue#109): Remove when Product is removed. attestation.Product = qp.Product() return attestation, nil } diff --git a/client/client_linux.go b/client/client_linux.go index 261cb8c..72ed35d 100644 --- a/client/client_linux.go +++ b/client/client_linux.go @@ -157,7 +157,12 @@ func (p *LinuxIoctlQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level if err != nil { return nil, err } - return append(report, certs...), nil + // Mix the platform info in with the auxblob. + extended, err := abi.ExtendedPlatformCertTable(certs) + if err != nil { + return nil, fmt.Errorf("invalid certificate table: %v", err) + } + return append(report, extended...), nil } // GetRawQuote returns byte format attestation plus certificate table via /dev/sev-guest ioctl. @@ -166,6 +171,8 @@ func (p *LinuxIoctlQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, err } // Product returns the current CPU's associated AMD SEV product information. +// +// Deprecated: Use ExtraPlatformInfoGUID from the cert table. func (*LinuxIoctlQuoteProvider) Product() *spb.SevProduct { return abi.SevProduct() } @@ -193,7 +200,12 @@ func (p *LinuxConfigFsQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, lev if err != nil { return nil, err } - return append(resp.OutBlob, resp.AuxBlob...), nil + // Mix the platform info in with the auxblob. + extended, err := abi.ExtendedPlatformCertTable(resp.AuxBlob) + if err != nil { + return nil, fmt.Errorf("invalid certificate table: %v", err) + } + return append(resp.OutBlob, extended...), nil } // GetRawQuote returns byte format attestation plus certificate table via ConfigFS. @@ -206,10 +218,17 @@ func (p *LinuxConfigFsQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, if err != nil { return nil, err } - return append(resp.OutBlob, resp.AuxBlob...), nil + // Mix the platform info in with the auxblob. + extended, err := abi.ExtendedPlatformCertTable(resp.AuxBlob) + if err != nil { + return nil, fmt.Errorf("invalid certificate table: %v", err) + } + return append(resp.OutBlob, extended...), nil } // Product returns the current CPU's associated AMD SEV product information. +// +// Deprecated: Use ExtraPlatformInfoGUID from the cert table. func (*LinuxConfigFsQuoteProvider) Product() *spb.SevProduct { return abi.SevProduct() } diff --git a/testing/mocks.go b/testing/mocks.go index d91ed80..782f799 100644 --- a/testing/mocks.go +++ b/testing/mocks.go @@ -184,7 +184,17 @@ func (p *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) { if err := abi.SetSignature(r, s, report); err != nil { return nil, fmt.Errorf("test error: could not set signature: %v", err) } - return append(report, p.Device.Certs...), nil + if p.Device.SevProduct == nil { + return nil, fmt.Errorf("mock SevProduct must not be nil") + } + extended, err := abi.ExtendPlatformCertTable(p.Device.Certs, &abi.ExtraPlatformInfo{ + Size: abi.ExtraPlatformInfoV0Size, + Cpuid1Eax: abi.MaskedCpuid1EaxFromSevProduct(p.Device.SevProduct), + }) + if err != nil { + return nil, err + } + return append(report, extended...), nil } // GetResponse controls how often (Occurrences) a certain response should be diff --git a/testing/test_cases.go b/testing/test_cases.go index 730400c..34b2663 100644 --- a/testing/test_cases.go +++ b/testing/test_cases.go @@ -24,6 +24,7 @@ import ( labi "github.com/google/go-sev-guest/client/linuxabi" "github.com/google/go-sev-guest/kds" spb "github.com/google/go-sev-guest/proto/sevsnp" + "github.com/google/logger" ) // userZeros defines a ReportData example that is all zeros @@ -253,12 +254,17 @@ func TcDevice(tcs []TestCase, opts *DeviceOptions) (*Device, error) { EsResult: tc.EsResult, } } + product := opts.Product + if product == nil { + logger.Warning("test missing sevproduct") + product = abi.DefaultSevProduct() + } return &Device{ ReportDataRsp: responses, Certs: certs, Signer: signer, Keys: opts.Keys, - SevProduct: opts.Product, + SevProduct: product, }, nil } diff --git a/tools/check/check_test.go b/tools/check/check_test.go index 4ee0f77..b7e33f3 100644 --- a/tools/check/check_test.go +++ b/tools/check/check_test.go @@ -132,7 +132,7 @@ func setField(p *checkpb.Policy, name string, value any) { } func bytesSetter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { v, err := hex.DecodeString(value) if err != nil { return true @@ -143,14 +143,14 @@ func bytesSetter(name string) setterFn { } func stringSetter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { setField(p, name, value) return false } } func boolSetter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { switch value { case "true": setField(p, name, true) @@ -165,7 +165,7 @@ func boolSetter(name string) setterFn { } func uint64setter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 64) if err != nil { return true @@ -176,7 +176,7 @@ func uint64setter(name string) setterFn { } func uint32setter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 32) if err != nil { return true @@ -187,7 +187,7 @@ func uint32setter(name string) setterFn { } func uint32valueSetter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 32) if err != nil { return true @@ -198,7 +198,7 @@ func uint32valueSetter(name string) setterFn { } func uint64valueSetter(name string) setterFn { - return func(p *checkpb.Policy, value string, t *testing.T) bool { + return func(p *checkpb.Policy, value string, _ *testing.T) bool { u, err := strconv.ParseUint(value, 10, 64) if err != nil { return true diff --git a/tools/lib/report/report_test.go b/tools/lib/report/report_test.go index 8746502..e486232 100644 --- a/tools/lib/report/report_test.go +++ b/tools/lib/report/report_test.go @@ -53,7 +53,7 @@ func initDevice() { for i := range ones32 { ones32[i] = 1 } - opts := &test.DeviceOptions{Now: now} + opts := &test.DeviceOptions{Now: now, Product: abi.DefaultSevProduct()} tcqp, err := test.TcQuoteProvider(tests, opts) if err != nil { panic(fmt.Sprintf("failed to create test device: %v", err)) @@ -172,26 +172,31 @@ func TestReadAttestation(t *testing.T) { func TestTransform(t *testing.T) { mu.Do(initDevice) - binout, err := Transform(input.attestation, "bin") - if err != nil { - t.Fatalf("Transform(_, \"bin\") = _, %v. Expect nil.", err) - } - if !bytes.Equal(binout, input.bincerts) { - t.Fatalf("Transform(_, \"bin\") = %v, nil. Expect %v.", binout, input.bincerts) - } - protoout, err := Transform(input.attestation, "proto") - if err != nil { - t.Fatalf("Transform(_, \"proto\") = _, %v. Expect nil.", err) - } - if !bytes.Equal(protoout, input.protocerts) { - t.Fatalf("Transform(_, \"proto\") = %v, nil. Expect %v.", protoout, input.protocerts) - } - textout, err := Transform(input.attestation, "textproto") - if err != nil { - t.Fatalf("Transform(_, \"textproto\") = _, %v. Expect nil.", err) - } - if !bytes.Equal(textout, input.textcerts) { - t.Fatalf("Transform(_, \"textproto\") = %v, nil. Expect %v.", string(textout), string(input.textcerts)) - } - + t.Run("bin", func(t *testing.T) { + binout, err := Transform(input.attestation, "bin") + if err != nil { + t.Fatalf("Transform(_, \"bin\") = _, %v. Expect nil.", err) + } + if !bytes.Equal(binout, input.bincerts) { + t.Fatalf("Transform(_, \"bin\") = %v, nil. Expect %v.", binout, input.bincerts) + } + }) + t.Run("proto", func(t *testing.T) { + protoout, err := Transform(input.attestation, "proto") + if err != nil { + t.Fatalf("Transform(_, \"proto\") = _, %v. Expect nil.", err) + } + if !bytes.Equal(protoout, input.protocerts) { + t.Fatalf("Transform(_, \"proto\") = %v, nil. Expect %v.", protoout, input.protocerts) + } + }) + t.Run("textproto", func(t *testing.T) { + textout, err := Transform(input.attestation, "textproto") + if err != nil { + t.Fatalf("Transform(_, \"textproto\") = _, %v. Expect nil.", err) + } + if !bytes.Equal(textout, input.textcerts) { + t.Fatalf("Transform(_, \"textproto\") = %v, nil. Expect %v.", string(textout), string(input.textcerts)) + } + }) } diff --git a/validate/validate_test.go b/validate/validate_test.go index 26a2c18..422bbdf 100644 --- a/validate/validate_test.go +++ b/validate/validate_test.go @@ -162,7 +162,8 @@ func TestValidateSnpAttestation(t *testing.T) { if err != nil { t.Fatal(err) } - qp0, err := test.TcQuoteProvider(test.TestCases(), &test.DeviceOptions{Now: now, Signer: sign0}) + qp0, err := test.TcQuoteProvider(test.TestCases(), + &test.DeviceOptions{Now: now, Signer: sign0, Product: abi.DefaultSevProduct()}) if err != nil { t.Fatal(err) } @@ -506,7 +507,7 @@ func TestCertTableOptions(t *testing.T) { PlatformInfo: &abi.SnpPlatformInfo{SMTEnabled: true}, CertTableOptions: map[string]*CertEntryOption{ - "00000000-feee-feee-0000-000000000000": {Kind: CertEntryRequire, Validate: func(_ *spb.Attestation, blob []byte) error { return nil }}, + "00000000-feee-feee-0000-000000000000": {Kind: CertEntryRequire, Validate: func(*spb.Attestation, []byte) error { return nil }}, }, }); err == nil || !strings.Contains(err.Error(), "required") { t.Errorf("SnpAttestation(_, &Options{CertTableOptions: require feee-feee}) = %v, want error to contain %s", err, "required") @@ -522,7 +523,7 @@ func TestCertTableOptions(t *testing.T) { } return nil }}, - "00000000-feee-feee-0000-000000000000": {Kind: CertEntryAllowMissing, Validate: func(_ *spb.Attestation, blob []byte) error { return errors.New("don't call me") }}, + "00000000-feee-feee-0000-000000000000": {Kind: CertEntryAllowMissing, Validate: func(*spb.Attestation, []byte) error { return errors.New("don't call me") }}, }, }); err != nil { t.Errorf("SnpAttestation(_, &Options{CertTableOptions: require c0de, allow feee-fee}) = %v, want nil", err) diff --git a/verify/verify.go b/verify/verify.go index 7fb1c7f..abf91d6 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -677,13 +677,48 @@ func SnpAttestation(attestation *spb.Attestation, options *Options) error { return SnpProtoReportSignature(report, endorsementKeyCert) } +func getProductFromCerts(attestation *spb.Attestation) *spb.SevProduct { + certs := abi.CertsFromProto(attestation.CertificateChain) + blob, err := certs.GetByGUIDString(abi.ExtraPlatformInfoGUID) + if err != nil { + return nil + } + info, err := abi.ParseExtraPlatformInfo(blob) + if err != nil { + return nil + } + return abi.SevProductFromCpuid1Eax(info.Cpuid1Eax) +} + +// Returns the product information in the attestation. +func getProduct(attestation *spb.Attestation) *spb.SevProduct { + product := getProductFromCerts(attestation) + if product != nil { + return product + } + // TODO(Issue#109): Remove. + return attestation.Product +} + +// Updates the attestation representation of the product. This is lossy given the product -> Cpuid1Eax translation. +func setProduct(attestation *spb.Attestation, product *spb.SevProduct) { + blob, _ := (&abi.ExtraPlatformInfo{ + Size: abi.ExtraPlatformInfoV0Size, + Cpuid1Eax: abi.MaskedCpuid1EaxFromSevProduct(product), + }).Marshal() + attestation.CertificateChain.Extras[abi.ExtraPlatformInfoGUID] = blob + // TODO(Issue#109): Remove + attestation.Product = product +} + // fillInAttestation uses AMD's KDS to populate any empty certificate field in the attestation's // certificate chain. func fillInAttestation(attestation *spb.Attestation, options *Options) error { var productOverridden bool - if attestation.Product == nil { + product := getProduct(attestation) + if product == nil { if options.Product != nil { - attestation.Product = options.Product + product = options.Product } else { logger.Warning("Attestation missing product information. KDS certificate may be invalid. Using default Milan-B1") attestation.Product = abi.DefaultSevProduct() @@ -693,7 +728,7 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { if options.DisableCertFetching { return nil } - product := kds.ProductString(attestation.Product) + productStr := kds.ProductString(product) getter := options.Getter if getter == nil { getter = trust.DefaultHTTPSGetter() @@ -709,7 +744,7 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { attestation.CertificateChain = chain } if len(chain.GetAskCert()) == 0 || len(chain.GetArkCert()) == 0 { - askark, err := trust.GetProductChain(product, info.SigningKey, getter) + askark, err := trust.GetProductChain(productStr, info.SigningKey, getter) if err != nil { return err } @@ -724,7 +759,7 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { switch info.SigningKey { case abi.VcekReportSigner: if len(chain.GetVcekCert()) == 0 { - vcekURL := kds.VCEKCertURL(product, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb())) + vcekURL := kds.VCEKCertURL(productStr, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb())) vcek, err := getter.Get(vcekURL) if err != nil { return &trust.AttestationRecreationErr{ @@ -743,7 +778,7 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { if err != nil { return err } - attestation.Product, err = kds.ParseProductName(exts.ProductName, abi.VcekReportSigner) + product, err = kds.ParseProductName(exts.ProductName, abi.VcekReportSigner) if err != nil { return err } @@ -759,7 +794,7 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { // Pass along the expected product information for VcekDER. fillInAttestation will ensure // that this is a noop if options.Product began as non-nil. - return updateProductExpectation(&options.Product, attestation.Product) + return updateProductExpectation(&options.Product, product) } // GetAttestationFromReport uses AMD's Key Distribution Service (KDS) to download the certificate @@ -768,7 +803,7 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error { func GetAttestationFromReport(report *spb.Report, options *Options) (*spb.Attestation, error) { result := &spb.Attestation{ Report: report, - CertificateChain: &spb.CertificateChain{}, + CertificateChain: &spb.CertificateChain{Extras: map[string][]byte{}}, } if err := fillInAttestation(result, options); err != nil { return nil, err @@ -788,7 +823,8 @@ func GetAttestationFromReport(report *spb.Report, options *Options) (*spb.Attest exts, _ = kds.VlekCertificateExtensions(parse(result.CertificateChain.VlekCert)) } if exts != nil { - result.Product, _ = kds.ParseProductName(exts.ProductName, info.SigningKey) + product, _ := kds.ParseProductName(exts.ProductName, info.SigningKey) + setProduct(result, product) } return result, nil } diff --git a/verify/verify_test.go b/verify/verify_test.go index 89372fe..fe5909b 100644 --- a/verify/verify_test.go +++ b/verify/verify_test.go @@ -161,7 +161,7 @@ func TestVerifyVcekCert(t *testing.T) { func TestSnpReportSignature(t *testing.T) { tests := test.TestCases() now := time.Date(2022, time.May, 3, 9, 0, 0, 0, time.UTC) - qp, err := test.TcQuoteProvider(tests, &test.DeviceOptions{Now: now}) + qp, err := test.TcQuoteProvider(tests, &test.DeviceOptions{Now: now, Product: abi.DefaultSevProduct()}) if err != nil { t.Fatal(err) } @@ -432,7 +432,7 @@ func TestOpenGetExtendedReportVerifyClose(t *testing.T) { tests := test.TestCases() qp, goodRoots, badRoots, kds := testclient.GetSevQuoteProvider(tests, &test.DeviceOptions{Now: time.Now()}, t) type reportGetter func(sg.QuoteProvider, [64]byte) (*pb.Attestation, error) - reportOnly := func(d sg.QuoteProvider, input [64]byte) (*pb.Attestation, error) { + reportOnly := func(qp sg.QuoteProvider, input [64]byte) (*pb.Attestation, error) { attestation, err := sg.GetQuoteProto(qp, input) if err != nil { return nil, err