Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for validating VLEK certificates #67

Merged
merged 1 commit into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 112 additions & 17 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -184,9 +187,8 @@ func ParseSnpPolicy(guestPolicy uint64) (SnpPolicy, error) {
if guestPolicy&uint64(1<<policyReserved1bit) == 0 {
return result, fmt.Errorf("policy[%d] is reserved, must be 1, got 0", policyReserved1bit)
}
validMask := uint64((1 << 21) - 1)
if guestPolicy&^validMask != 0 {
return result, fmt.Errorf("policy[63:21] are reserved mbz, got 0x%x", guestPolicy)
if err := mbz64(guestPolicy, "policy", 63, 21); err != nil {
return result, err
}
result.ABIMinor = uint8(guestPolicy & 0xff)
result.ABIMajor = uint8((guestPolicy >> 8) & 0xff)
Expand Down Expand Up @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some comments here? It's not quite clear what being checked here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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
}
Expand Down Expand Up @@ -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[:])
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
42 changes: 40 additions & 2 deletions abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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{}
Expand Down
8 changes: 7 additions & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Loading
Loading