mirror of
https://github.com/google/go-attestation.git
synced 2025-04-15 15:06:43 +00:00
Implement extractor for determining secure boot state (#148)
This commit is contained in:
parent
34338f547c
commit
e134551bb0
@ -19,7 +19,9 @@ import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
@ -77,6 +79,29 @@ type Event struct {
|
||||
// match their data to their digest.
|
||||
}
|
||||
|
||||
func (e *Event) digestEquals(b []byte) error {
|
||||
if len(e.Digest) == 0 {
|
||||
return errors.New("no digests present")
|
||||
}
|
||||
|
||||
switch len(e.Digest) {
|
||||
case crypto.SHA256.Size():
|
||||
s := sha256.Sum256(b)
|
||||
if bytes.Equal(s[:], e.Digest) {
|
||||
return nil
|
||||
}
|
||||
case crypto.SHA1.Size():
|
||||
s := sha1.Sum(b)
|
||||
if bytes.Equal(s[:], e.Digest) {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cannot compare hash of length %d", len(e.Digest))
|
||||
}
|
||||
|
||||
return fmt.Errorf("digest (len %d) does not match", len(e.Digest))
|
||||
}
|
||||
|
||||
// EventLog is a parsed measurement log. This contains unverified data representing
|
||||
// boot events that must be replayed against PCR values to determine authenticity.
|
||||
type EventLog struct {
|
||||
|
@ -134,7 +134,7 @@ func TestParseEventLogEventSizeTooLarge(t *testing.T) {
|
||||
0x31, 0x39, 0x36, 0x33, 0x39, 0x34, 0x34, 0x37, 0x39, 0x32,
|
||||
0x31, 0x32, 0x32, 0x37, 0x39, 0x30, 0x34, 0x30, 0x31, 0x6d,
|
||||
|
||||
// Even size (3.183 GB)
|
||||
// Event size (3.183 GB)
|
||||
0xbd, 0xbf, 0xef, 0x47,
|
||||
|
||||
// "event data"
|
||||
@ -251,7 +251,7 @@ func TestParseSpecIDEvent(t *testing.T) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, alg := range(spec.algs) {
|
||||
for _, alg := range spec.algs {
|
||||
algs = append(algs, alg.ID)
|
||||
}
|
||||
|
||||
|
371
attest/internal/events.go
Normal file
371
attest/internal/events.go
Normal file
@ -0,0 +1,371 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxNameLen is the maximum accepted byte length for a name field.
|
||||
// This value should be larger than any reasonable value.
|
||||
maxNameLen = 2048
|
||||
// maxDataLen is the maximum size in bytes of a variable data field.
|
||||
// This value should be larger than any reasonable value.
|
||||
maxDataLen = 1024 * 1024 // 1 Megabyte.
|
||||
)
|
||||
|
||||
// GUIDs representing the contents of an UEFI_SIGNATURE_LIST.
|
||||
var (
|
||||
hashSHA256SigGUID = efiGUID{0xc1c41626, 0x504c, 0x4092, [8]byte{0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28}}
|
||||
hashSHA1SigGUID = efiGUID{0x826ca512, 0xcf10, 0x4ac9, [8]byte{0xb1, 0x87, 0xbe, 0x01, 0x49, 0x66, 0x31, 0xbd}}
|
||||
hashSHA224SigGUID = efiGUID{0x0b6e5233, 0xa65c, 0x44c9, [8]byte{0x94, 0x07, 0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd}}
|
||||
hashSHA384SigGUID = efiGUID{0xff3e5307, 0x9fd0, 0x48c9, [8]byte{0x85, 0xf1, 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x01}}
|
||||
hashSHA512SigGUID = efiGUID{0x093e0fae, 0xa6c4, 0x4f50, [8]byte{0x9f, 0x1b, 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a}}
|
||||
keyRSA2048SigGUID = efiGUID{0x3c5766e8, 0x269c, 0x4e34, [8]byte{0xaa, 0x14, 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6}}
|
||||
certRSA2048SHA256SigGUID = efiGUID{0xe2b36190, 0x879b, 0x4a3d, [8]byte{0xad, 0x8d, 0xf2, 0xe7, 0xbb, 0xa3, 0x27, 0x84}}
|
||||
certRSA2048SHA1SigGUID = efiGUID{0x67f8444f, 0x8743, 0x48f1, [8]byte{0xa3, 0x28, 0x1e, 0xaa, 0xb8, 0x73, 0x60, 0x80}}
|
||||
certX509SigGUID = efiGUID{0xa5c059a1, 0x94e4, 0x4aa7, [8]byte{0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72}}
|
||||
certHashSHA256SigGUID = efiGUID{0x3bd2a492, 0x96c0, 0x4079, [8]byte{0xb4, 0x20, 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed}}
|
||||
certHashSHA384SigGUID = efiGUID{0x7076876e, 0x80c2, 0x4ee6, [8]byte{0xaa, 0xd2, 0x28, 0xb3, 0x49, 0xa6, 0x86, 0x5b}}
|
||||
certHashSHA512SigGUID = efiGUID{0x446dbf63, 0x2502, 0x4cda, [8]byte{0xbc, 0xfa, 0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d}}
|
||||
)
|
||||
|
||||
// EventType describes the type of event signalled in the event log.
|
||||
type EventType uint32
|
||||
|
||||
// BIOS Events (TCG PC Client Specific Implementation Specification for Conventional BIOS 1.21)
|
||||
const (
|
||||
PrebootCert EventType = 0x00000000
|
||||
PostCode EventType = 0x00000001
|
||||
unused EventType = 0x00000002
|
||||
NoAction EventType = 0x00000003
|
||||
Separator EventType = 0x00000004
|
||||
Action EventType = 0x00000005
|
||||
EventTag EventType = 0x00000006
|
||||
SCRTMContents EventType = 0x00000007
|
||||
SCRTMVersion EventType = 0x00000008
|
||||
CpuMicrocode EventType = 0x00000009
|
||||
PlatformConfigFlags EventType = 0x0000000A
|
||||
TableOfDevices EventType = 0x0000000B
|
||||
CompactHash EventType = 0x0000000C
|
||||
Ipl EventType = 0x0000000D
|
||||
IplPartitionData EventType = 0x0000000E
|
||||
NonhostCode EventType = 0x0000000F
|
||||
NonhostConfig EventType = 0x00000010
|
||||
NonhostInfo EventType = 0x00000011
|
||||
OmitBootDeviceEvents EventType = 0x00000012
|
||||
)
|
||||
|
||||
// EFI Events (TCG EFI Platform Specification Version 1.22)
|
||||
const (
|
||||
EFIEventBase EventType = 0x80000000
|
||||
EFIVariableDriverConfig EventType = 0x80000001
|
||||
EFIVariableBoot EventType = 0x80000002
|
||||
EFIBootServicesApplication EventType = 0x80000003
|
||||
EFIBootServicesDriver EventType = 0x80000004
|
||||
EFIRuntimeServicesDriver EventType = 0x80000005
|
||||
EFIGPTEvent EventType = 0x80000006
|
||||
EFIAction EventType = 0x80000007
|
||||
EFIPlatformFirmwareBlob EventType = 0x80000008
|
||||
EFIHandoffTables EventType = 0x80000009
|
||||
EFIHCRTMEvent EventType = 0x80000010
|
||||
EFIVariableAuthority EventType = 0x800000e0
|
||||
)
|
||||
|
||||
var eventTypeNames = map[EventType]string{
|
||||
PrebootCert: "Preboot Cert",
|
||||
PostCode: "POST Code",
|
||||
unused: "Unused",
|
||||
NoAction: "No Action",
|
||||
Separator: "Separator",
|
||||
Action: "Action",
|
||||
EventTag: "Event Tag",
|
||||
SCRTMContents: "S-CRTM Contents",
|
||||
SCRTMVersion: "S-CRTM Version",
|
||||
CpuMicrocode: "CPU Microcode",
|
||||
PlatformConfigFlags: "Platform Config Flags",
|
||||
TableOfDevices: "Table of Devices",
|
||||
CompactHash: "Compact Hash",
|
||||
Ipl: "IPL",
|
||||
IplPartitionData: "IPL Partition Data",
|
||||
NonhostCode: "Non-Host Code",
|
||||
NonhostConfig: "Non-HostConfig",
|
||||
NonhostInfo: "Non-Host Info",
|
||||
OmitBootDeviceEvents: "Omit Boot Device Events",
|
||||
|
||||
EFIEventBase: "EFI Event Base",
|
||||
EFIVariableDriverConfig: "EFI Variable Driver Config",
|
||||
EFIVariableBoot: "EFI Variable Boot",
|
||||
EFIBootServicesApplication: "EFI Boot Services Application",
|
||||
EFIBootServicesDriver: "EFI Boot Services Driver",
|
||||
EFIRuntimeServicesDriver: "EFI Runtime Services Driver",
|
||||
EFIGPTEvent: "EFI GPT Event",
|
||||
EFIAction: "EFI Action",
|
||||
EFIPlatformFirmwareBlob: "EFI Platform Firmware Blob",
|
||||
EFIVariableAuthority: "EFI Variable Authority",
|
||||
EFIHandoffTables: "EFI Handoff Tables",
|
||||
EFIHCRTMEvent: "EFI H-CRTM Event",
|
||||
}
|
||||
|
||||
func (e EventType) String() string {
|
||||
if s, ok := eventTypeNames[e]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("EventType(0x%x)", uint32(e))
|
||||
}
|
||||
|
||||
// UntrustedParseEventType returns the event type indicated by
|
||||
// the provided value.
|
||||
func UntrustedParseEventType(et uint32) (EventType, error) {
|
||||
// "The value associated with a UEFI specific platform event type MUST be in
|
||||
// the range between 0x80000000 and 0x800000FF, inclusive."
|
||||
if (et < 0x80000000 && et > 0x800000FF) || (et < 0x0 && et > 0x12) {
|
||||
return EventType(0), fmt.Errorf("event type not between [0x0, 0x12] or [0x80000000, 0x800000FF]: got %#x", et)
|
||||
}
|
||||
if _, ok := eventTypeNames[EventType(et)]; !ok {
|
||||
return EventType(0), fmt.Errorf("unknown event type %#x", et)
|
||||
}
|
||||
return EventType(et), nil
|
||||
}
|
||||
|
||||
// efiGUID represents the EFI_GUID type.
|
||||
// See section "2.3.1 Data Types" in the specification for more information.
|
||||
// type efiGUID [16]byte
|
||||
type efiGUID struct {
|
||||
Data1 uint32
|
||||
Data2 uint16
|
||||
Data3 uint16
|
||||
Data4 [8]byte
|
||||
}
|
||||
|
||||
func (d efiGUID) String() string {
|
||||
var u [8]byte
|
||||
binary.BigEndian.PutUint32(u[:4], d.Data1)
|
||||
binary.BigEndian.PutUint16(u[4:6], d.Data2)
|
||||
binary.BigEndian.PutUint16(u[6:8], d.Data3)
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], d.Data4[:2], d.Data4[2:])
|
||||
}
|
||||
|
||||
// UEFIVariableDataHeader represents the leading fixed-size fields
|
||||
// within UEFI_VARIABLE_DATA.
|
||||
type UEFIVariableDataHeader struct {
|
||||
VariableName efiGUID
|
||||
UnicodeNameLength uint64 // uintN
|
||||
VariableDataLength uint64 // uintN
|
||||
}
|
||||
|
||||
// UEFIVariableData represents the UEFI_VARIABLE_DATA structure.
|
||||
type UEFIVariableData struct {
|
||||
Header UEFIVariableDataHeader
|
||||
UnicodeName []uint16
|
||||
VariableData []byte // []int8
|
||||
}
|
||||
|
||||
// ParseUEFIVariableData parses the data section of an event structured as
|
||||
// a UEFI variable.
|
||||
//
|
||||
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100
|
||||
func ParseUEFIVariableData(r io.Reader) (ret UEFIVariableData, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &ret.Header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ret.Header.UnicodeNameLength > maxNameLen {
|
||||
return UEFIVariableData{}, fmt.Errorf("unicode name too long: %d > %d", ret.Header.UnicodeNameLength, maxNameLen)
|
||||
}
|
||||
ret.UnicodeName = make([]uint16, ret.Header.UnicodeNameLength)
|
||||
for i := 0; uint64(i) < ret.Header.UnicodeNameLength; i++ {
|
||||
err = binary.Read(r, binary.LittleEndian, &ret.UnicodeName[i])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ret.Header.VariableDataLength > maxDataLen {
|
||||
return UEFIVariableData{}, fmt.Errorf("variable data too long: %d > %d", ret.Header.VariableDataLength, maxDataLen)
|
||||
}
|
||||
ret.VariableData = make([]byte, ret.Header.VariableDataLength)
|
||||
_, err = io.ReadFull(r, ret.VariableData)
|
||||
return
|
||||
}
|
||||
|
||||
func (v *UEFIVariableData) VarName() string {
|
||||
return string(utf16.Decode(v.UnicodeName))
|
||||
}
|
||||
|
||||
func (v *UEFIVariableData) SignatureData() (certs []x509.Certificate, hashes [][]byte, err error) {
|
||||
return parseEfiSignatureList(v.VariableData)
|
||||
}
|
||||
|
||||
// UEFIVariableAuthority describes the contents of a UEFI variable authority
|
||||
// event.
|
||||
type UEFIVariableAuthority struct {
|
||||
Certs []x509.Certificate
|
||||
}
|
||||
|
||||
// ParseUEFIVariableAuthority parses the data section of an event structured as
|
||||
// a UEFI variable authority.
|
||||
//
|
||||
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789
|
||||
func ParseUEFIVariableAuthority(r io.Reader) (UEFIVariableAuthority, error) {
|
||||
v, err := ParseUEFIVariableData(r)
|
||||
if err != nil {
|
||||
return UEFIVariableAuthority{}, err
|
||||
}
|
||||
certs, err := parseEfiSignature(v.VariableData)
|
||||
if err != nil {
|
||||
return UEFIVariableAuthority{}, err
|
||||
}
|
||||
return UEFIVariableAuthority{Certs: certs}, nil
|
||||
}
|
||||
|
||||
// efiSignatureData represents the EFI_SIGNATURE_DATA type.
|
||||
// See section "31.4.1 Signature Database" in the specification for more information.
|
||||
type efiSignatureData struct {
|
||||
SignatureOwner efiGUID
|
||||
SignatureData []byte // []int8
|
||||
}
|
||||
|
||||
// efiSignatureList represents the EFI_SIGNATURE_LIST type.
|
||||
// See section "31.4.1 Signature Database" in the specification for more information.
|
||||
type efiSignatureListHeader struct {
|
||||
SignatureType efiGUID
|
||||
SignatureListSize uint32
|
||||
SignatureHeaderSize uint32
|
||||
SignatureSize uint32
|
||||
}
|
||||
|
||||
type efiSignatureList struct {
|
||||
Header efiSignatureListHeader
|
||||
SignatureData []byte
|
||||
Signatures []byte
|
||||
}
|
||||
|
||||
// parseEfiSignatureList parses a EFI_SIGNATURE_LIST structure.
|
||||
// The structure and related GUIDs are defined at:
|
||||
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1790
|
||||
func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
|
||||
if len(b) < 28 {
|
||||
// Being passed an empty signature list here appears to be valid
|
||||
return nil, nil, nil
|
||||
}
|
||||
signatures := efiSignatureList{}
|
||||
buf := bytes.NewReader(b)
|
||||
certificates := []x509.Certificate{}
|
||||
hashes := [][]byte{}
|
||||
|
||||
for buf.Len() > 0 {
|
||||
err := binary.Read(buf, binary.LittleEndian, &signatures.Header)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if signatures.Header.SignatureHeaderSize > maxDataLen {
|
||||
return nil, nil, fmt.Errorf("signature header too large: %d > %d", signatures.Header.SignatureHeaderSize, maxDataLen)
|
||||
}
|
||||
if signatures.Header.SignatureListSize > maxDataLen {
|
||||
return nil, nil, fmt.Errorf("signature list too large: %d > %d", signatures.Header.SignatureListSize, maxDataLen)
|
||||
}
|
||||
|
||||
signatureType := signatures.Header.SignatureType
|
||||
switch signatureType {
|
||||
case certX509SigGUID: // X509 certificate
|
||||
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
|
||||
signature := efiSignatureData{}
|
||||
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
|
||||
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(signature.SignatureData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sigOffset += int(signatures.Header.SignatureSize)
|
||||
certificates = append(certificates, *cert)
|
||||
}
|
||||
case hashSHA256SigGUID: // SHA256
|
||||
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
|
||||
signature := efiSignatureData{}
|
||||
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
|
||||
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hashes = append(hashes, signature.SignatureData)
|
||||
sigOffset += int(signatures.Header.SignatureSize)
|
||||
}
|
||||
case keyRSA2048SigGUID:
|
||||
err = errors.New("unhandled RSA2048 key")
|
||||
case certRSA2048SHA256SigGUID:
|
||||
err = errors.New("unhandled RSA2048-SHA256 key")
|
||||
case hashSHA1SigGUID:
|
||||
err = errors.New("unhandled SHA1 hash")
|
||||
case certRSA2048SHA1SigGUID:
|
||||
err = errors.New("unhandled RSA2048-SHA1 key")
|
||||
case hashSHA224SigGUID:
|
||||
err = errors.New("unhandled SHA224 hash")
|
||||
case hashSHA384SigGUID:
|
||||
err = errors.New("unhandled SHA384 hash")
|
||||
case hashSHA512SigGUID:
|
||||
err = errors.New("unhandled SHA512 hash")
|
||||
case certHashSHA256SigGUID:
|
||||
err = errors.New("unhandled X509-SHA256 hash metadata")
|
||||
case certHashSHA384SigGUID:
|
||||
err = errors.New("unhandled X509-SHA384 hash metadata")
|
||||
case certHashSHA512SigGUID:
|
||||
err = errors.New("unhandled X509-SHA512 hash metadata")
|
||||
default:
|
||||
err = fmt.Errorf("unhandled signature type %s", signatureType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return certificates, hashes, nil
|
||||
}
|
||||
|
||||
// EFISignatureData represents the EFI_SIGNATURE_DATA type.
|
||||
// See section "31.4.1 Signature Database" in the specification
|
||||
// for more information.
|
||||
type EFISignatureData struct {
|
||||
SignatureOwner efiGUID
|
||||
SignatureData []byte // []int8
|
||||
}
|
||||
|
||||
func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
|
||||
certificates := []x509.Certificate{}
|
||||
|
||||
if len(b) < 16 {
|
||||
return nil, fmt.Errorf("invalid signature: buffer smaller than header (%d < %d)", len(b), 16)
|
||||
}
|
||||
|
||||
buf := bytes.NewReader(b)
|
||||
signature := EFISignatureData{}
|
||||
signature.SignatureData = make([]byte, len(b)-16)
|
||||
|
||||
if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner); err != nil {
|
||||
return certificates, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureData); err != nil {
|
||||
return certificates, err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(signature.SignatureData)
|
||||
if err == nil {
|
||||
certificates = append(certificates, *cert)
|
||||
}
|
||||
return certificates, err
|
||||
}
|
32
attest/internal/events_test.go
Normal file
32
attest/internal/events_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestParseUEFIVariableData(t *testing.T) {
|
||||
data := []byte{0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0xd, 0x0, 0xe0, 0x98,
|
||||
0x3, 0x2b, 0x8c, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x53, 0x0, 0x65, 0x0, 0x63, 0x0, 0x75, 0x0, 0x72, 0x0,
|
||||
0x65, 0x0, 0x42, 0x0, 0x6f, 0x0, 0x6f, 0x0, 0x74, 0x0, 0x1}
|
||||
want := UEFIVariableData{
|
||||
Header: UEFIVariableDataHeader{
|
||||
VariableName: efiGUID{Data1: 0x8be4df61, Data2: 0x93ca, Data3: 0x11d2, Data4: [8]uint8{0xaa, 0xd, 0x0, 0xe0, 0x98, 0x3, 0x2b, 0x8c}},
|
||||
UnicodeNameLength: 0xa,
|
||||
VariableDataLength: 0x1,
|
||||
},
|
||||
UnicodeName: []uint16{0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x42, 0x6f, 0x6f, 0x74},
|
||||
VariableData: []uint8{0x1},
|
||||
}
|
||||
|
||||
got, err := ParseUEFIVariableData(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseEFIVariableData() failed: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("ParseUEFIVariableData() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
181
attest/secureboot.go
Normal file
181
attest/secureboot.go
Normal file
@ -0,0 +1,181 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/google/go-attestation/attest/internal"
|
||||
)
|
||||
|
||||
// SecurebootState describes the secure boot status of a machine, as determined
|
||||
// by processing its event log.
|
||||
type SecurebootState struct {
|
||||
Enabled bool
|
||||
|
||||
// PlatformKeys enumerates keys which can sign a key exchange key.
|
||||
PlatformKeys []x509.Certificate
|
||||
// PlatformKeys enumerates key hashes which can sign a key exchange key.
|
||||
PlatformKeyHashes [][]byte
|
||||
|
||||
// ExchangeKeys enumerates keys which can sign a database of permitted or
|
||||
// forbidden keys.
|
||||
ExchangeKeys []x509.Certificate
|
||||
// ExchangeKeyHashes enumerates key hashes which can sign a database or
|
||||
// permitted or forbidden keys.
|
||||
ExchangeKeyHashes [][]byte
|
||||
|
||||
// PermittedKeys enumerates keys which may sign binaries to run.
|
||||
PermittedKeys []x509.Certificate
|
||||
// PermittedHashes enumerates hashes which permit binaries to run.
|
||||
PermittedHashes [][]byte
|
||||
|
||||
// ForbiddenKeys enumerates keys which must not permit a binary to run.
|
||||
ForbiddenKeys []x509.Certificate
|
||||
// ForbiddenKeys enumerates hashes which must not permit a binary to run.
|
||||
ForbiddenHashes [][]byte
|
||||
|
||||
// PreSeparatorAuthority describes the use of a secure-boot key to authorize
|
||||
// the execution of a binary before the separator.
|
||||
PreSeparatorAuthority []x509.Certificate
|
||||
// PostSeparatorAuthority describes the use of a secure-boot key to authorize
|
||||
// the execution of a binary after the separator.
|
||||
PostSeparatorAuthority []x509.Certificate
|
||||
}
|
||||
|
||||
// ParseSecurebootState parses a series of events to determine the
|
||||
// configuration of secure boot on a device. An error is returned if
|
||||
// the state cannot be determined, or if the event log is structured
|
||||
// in such a way that it may have been tampered post-execution of
|
||||
// platform firmware.
|
||||
func ParseSecurebootState(events []Event) (*SecurebootState, error) {
|
||||
// This algorithm verifies the following:
|
||||
// - All events in PCR 7 have event types which are expected in PCR 7.
|
||||
// - All events are parsable according to their event type.
|
||||
// - All events have digests values corresponding to their data/event type.
|
||||
// - No unverifiable events were present.
|
||||
// - All variables are specified before the separator and never duplicated.
|
||||
// - The SecureBoot variable has a value of 0 or 1.
|
||||
// - If SecureBoot was 1 (enabled), authority events were present indicating
|
||||
// keys were used to perform verification.
|
||||
// - If SecureBoot was 1 (enabled), platform + exchange + database keys
|
||||
// were specified.
|
||||
// - No UEFI debugger was attached.
|
||||
|
||||
var (
|
||||
out SecurebootState
|
||||
seenSeparator bool
|
||||
seenAuthority bool
|
||||
seenVars = map[string]bool{}
|
||||
)
|
||||
|
||||
for _, e := range events {
|
||||
if e.Index != 7 {
|
||||
continue
|
||||
}
|
||||
|
||||
et, err := internal.UntrustedParseEventType(uint32(e.Type))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unrecognised event type: %v", err)
|
||||
}
|
||||
|
||||
digestVerify := e.digestEquals(e.Data)
|
||||
switch et {
|
||||
case internal.Separator:
|
||||
if seenSeparator {
|
||||
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
|
||||
}
|
||||
seenSeparator = true
|
||||
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
|
||||
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
|
||||
}
|
||||
if digestVerify != nil {
|
||||
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
|
||||
}
|
||||
|
||||
case internal.EFIAction:
|
||||
if string(e.Data) == "UEFI Debug Mode" {
|
||||
return nil, errors.New("a UEFI debugger was present during boot")
|
||||
}
|
||||
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
|
||||
|
||||
case internal.EFIVariableDriverConfig:
|
||||
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
|
||||
}
|
||||
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
|
||||
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
|
||||
}
|
||||
seenVars[v.VarName()] = true
|
||||
if seenSeparator {
|
||||
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
|
||||
}
|
||||
|
||||
if digestVerify != nil {
|
||||
return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
|
||||
}
|
||||
|
||||
switch v.VarName() {
|
||||
case "SecureBoot":
|
||||
if len(v.VariableData) != 1 {
|
||||
return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
|
||||
}
|
||||
out.Enabled = v.VariableData[0] == 1
|
||||
case "PK":
|
||||
if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
|
||||
return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
|
||||
}
|
||||
case "KEK":
|
||||
if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
|
||||
return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
|
||||
}
|
||||
case "db":
|
||||
if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
|
||||
return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
|
||||
}
|
||||
case "dbx":
|
||||
if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
|
||||
return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
|
||||
}
|
||||
}
|
||||
|
||||
case internal.EFIVariableAuthority:
|
||||
a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
|
||||
}
|
||||
seenAuthority = true
|
||||
if digestVerify != nil {
|
||||
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
|
||||
}
|
||||
if !seenSeparator {
|
||||
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
|
||||
} else {
|
||||
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected event type: %v", et)
|
||||
}
|
||||
}
|
||||
|
||||
if !out.Enabled {
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
if !seenAuthority {
|
||||
return nil, errors.New("secure boot was enabled but no key was used")
|
||||
}
|
||||
if len(out.PlatformKeys) == 0 && len(out.PlatformKeyHashes) == 0 {
|
||||
return nil, errors.New("secure boot was enabled but no platform keys were known")
|
||||
}
|
||||
if len(out.ExchangeKeys) == 0 && len(out.ExchangeKeyHashes) == 0 {
|
||||
return nil, errors.New("secure boot was enabled but no key exchange keys were known")
|
||||
}
|
||||
if len(out.PermittedKeys) == 0 && len(out.PermittedHashes) == 0 {
|
||||
return nil, errors.New("secure boot was enabled but no keys or hashes were permitted")
|
||||
}
|
||||
return &out, nil
|
||||
}
|
36
attest/secureboot_test.go
Normal file
36
attest/secureboot_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSecureBoot(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("testdata/windows_gcp_shielded_vm.json")
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
var dump Dump
|
||||
if err := json.Unmarshal(data, &dump); err != nil {
|
||||
t.Fatalf("parsing test data: %v", err)
|
||||
}
|
||||
|
||||
el, err := ParseEventLog(dump.Log.Raw)
|
||||
if err != nil {
|
||||
t.Fatalf("parsing event log: %v", err)
|
||||
}
|
||||
events, err := el.Verify(dump.Log.PCRs)
|
||||
if err != nil {
|
||||
t.Fatalf("validating event log: %v", err)
|
||||
}
|
||||
|
||||
sbState, err := ParseSecurebootState(events)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractSecurebootState() failed: %v", err)
|
||||
}
|
||||
|
||||
if got, want := sbState.Enabled, true; got != want {
|
||||
t.Errorf("secureboot.Enabled = %v, want %v", got, want)
|
||||
}
|
||||
}
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/google/certificate-transparency-go v1.0.22-0.20190605205155-41fc2ef3a2a8
|
||||
github.com/google/go-cmp v0.3.1
|
||||
github.com/google/go-tpm v0.2.1-0.20191015210219-431489f43254
|
||||
github.com/google/go-tpm-tools v0.1.1
|
||||
github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad
|
||||
|
2
go.sum
2
go.sum
@ -12,6 +12,8 @@ github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLm
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.0.22-0.20190605205155-41fc2ef3a2a8 h1:G3Wse9lGL7PmAl2jqdr0HgwhPkGA5KHu7guIPREa7DU=
|
||||
github.com/google/certificate-transparency-go v1.0.22-0.20190605205155-41fc2ef3a2a8/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4 h1:GNNkIb6NSjYfw+KvgUFW590mcgsSFihocSrbXct1sEw=
|
||||
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
|
||||
github.com/google/go-tpm v0.2.1-0.20190910203116-33a9c3f38379/go.mod h1:gTv8GNuqS7CI+tQWrpt5BMMaD5W3G+dZULQLhhAKT5c=
|
||||
|
Loading…
x
Reference in New Issue
Block a user