Skip to content

Commit

Permalink
Add SevProduct raw cert representation.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
deeglaze committed Feb 12, 2024
1 parent 312e524 commit 819b39a
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 73 deletions.
139 changes: 127 additions & 12 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand All @@ -910,10 +923,112 @@ 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{
Name: pb.SevProduct_SEV_PRODUCT_MILAN,
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())
}
100 changes: 88 additions & 12 deletions abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
})
}
}
5 changes: 5 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 819b39a

Please sign in to comment.