diff --git a/attest/attest-tool/internal/eventlog/eventlog.go b/attest/attest-tool/internal/eventlog/eventlog.go new file mode 100644 index 0000000..b85797d --- /dev/null +++ b/attest/attest-tool/internal/eventlog/eventlog.go @@ -0,0 +1,185 @@ +// Copyright 2019 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Package eventlog implements experimental logic for parsing the TCG event log format. +package eventlog + +import "fmt" + +// eventType indicates what kind of data an event is reporting. +type eventType uint32 + +func isReserved(t eventType) bool { + if 0x00000013 <= t && t <= 0x0000FFFF { + return true + } + if 0x800000E1 <= t && t <= 0x8000FFFF { + return true + } + return false +} + +// String returns the name as defined by the TCG specification. +func (e eventType) String() string { + if s, ok := eventTypeNames[e]; ok { + return s + } + s := fmt.Sprintf("eventType(0x%08x)", int(e)) + if isReserved(e) { + s += " (reserved)" + } + return s +} + +const ( + // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=103 + + // Reserved for future use. + evPrebootCert eventType = 0x00000000 + + // Host platform trust chain measurements. The event data can contain one of + // the following, indicating different points of boot: "POST CODE", "SMM CODE", + // "ACPI DATA", "BIS CODE", "Embedded UEFI Driver". + // + // PCR[0] MUST be extended with this event type. + // + // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=38 + evPostCode eventType = 0x00000001 + + // The event type was never used and is considered reserved. + evUnused eventType = 0x00000002 + + // Used for PCRs[0,6]. This event type doesn't extend the PCR, the digest MUST + // be all zeros, and the data holds information intended for parsers such as + // delimiting a switch to the agile crypto event format. + // + // This event MUST NOT extend any PCR + evNoAction eventType = 0x00000003 + + // Delineates the point where the Platform Firmware relinquishes control of TPM + // measurements to the operating system. + // + // Event data size MUST contain either 0x00000000 or 0xFFFFFFFF, the digest MUST + // match the data. + // + // This event MUST extend the PCRs 0 through 7 inclusive. + evSeparator eventType = 0x00000004 + + // An event indicating a particular action in the boot sequence, for example + // "User Password Entered" or "Booting BCV Device s". + // + // The digests field contains the tagged hash of the event field for each PCR bank. + // + // Used for PCRs [1, 2, 3, 4, 5, and 6]. + evAction eventType = 0x00000005 + + // Used for PCRs defined for OS and application usage. The digest field MUST + // contain a hash of the data. The data contains a TCG_PCClientTaggedEvent + // sructure. + evEventTag eventType = 0x00000006 + + // Used for PCR[0] only. The digest contains the hash of the SRTM for each PCR + // bank. The data is informative and not expected to match the digest. + evSCRTMContents eventType = 0x00000007 + evSCRTMVersion eventType = 0x00000008 + + // The digests field contains the tagged hash of the microcode patch applied for + // each PCR bank. The data is informative and not expected to match the digest. + evCUPMicrocode eventType = 0x00000009 + + // TODO(ericchiang): explain these events + evPlatformConfigFiles eventType = 0x0000000A + evTableOfDevices eventType = 0x0000000B + + // Can be used for any PCRs except 0, 1, 2, or 3. + evCompactHash eventType = 0x0000000C + + // IPL events are deprecated + evIPL eventType = 0x0000000D + evIPLPartitionData eventType = 0x0000000E + + // Used for PCR[0] only. + // + // TODO(ericchiang): explain these events + evNonhostCode eventType = 0x0000000F + evNonhostConfig eventType = 0x00000010 + evNonhostInfo eventType = 0x00000011 + evOmitBootDeviceEvents eventType = 0x00000012 + + // The following events are UEFI specific. + + // Data contains a UEFI_VARIABLE_DATA structure. + evEFIVariableDriverConfig eventType = 0x80000001 // PCR[1,3,5] + evEFIVariableBoot eventType = 0x80000002 // PCR[1] + + // Data contains a UEFI_IMAGE_LOAD_EVENT structure. + evEFIBootServicesApplication eventType = 0x80000003 // PCR[2,4] + evEFIBootServicesDriver eventType = 0x80000004 // PCR[0,2] + evEFIRuntimeServicesDriver eventType = 0x80000005 // PCR[2,4] + + // Data contains a UEFI_GPT_DATA structure. + evEFIGPTEvent eventType = 0x80000006 // PCR[5] + + evEFIAction eventType = 0x80000007 // PCR[1,2,3,4,5,6,7] + + // Data contains a UEFI_PLATFORM_FIRMWARE_BLOB structure. + evEFIPlatformFirmwareBlob eventType = 0x80000008 // PCR[0,2,4] + + // Data contains a UEFI_HANDOFF_TABLE_POINTERS structure. + evEFIHandoffTables eventType = 0x80000009 // PCR[1] + + // The digests field contains the tagged hash of the H-CRTM event + // data for each PCR bank. + // + // The Event Data MUST be the string: “HCRTM”. + evEFIHCRTMEvent eventType = 0x80000010 // PCR[0] + + // Data contains a UEFI_VARIABLE_DATA structure. + evEFIVariableAuthority eventType = 0x800000E0 // PCR[7] +) + +var eventTypeNames = map[eventType]string{ + evPrebootCert: "EV_PREBOOT_CERT", + evPostCode: "EV_POST_CODE", + evUnused: "EV_UNUSED", + evNoAction: "EV_NO_ACTION", + evSeparator: "EV_SEPARATOR", + evAction: "EV_ACTION", + evEventTag: "EV_EVENT_TAG", + evSCRTMContents: "EV_S_CRTM_CONTENTS", + evSCRTMVersion: "EV_S_CRTM_VERSION", + evCUPMicrocode: "EV_CPU_MICROCODE", + evPlatformConfigFiles: "EV_PLATFORM_CONFIG_FLAGS", + evTableOfDevices: "EV_TABLE_OF_DEVICES", + evCompactHash: "EV_COMPACT_HASH", + evIPL: "EV_IPL (deprecated)", + evIPLPartitionData: "EV_IPL_PARTITION_DATA (deprecated)", + evNonhostCode: "EV_NONHOST_CODE", + evNonhostConfig: "EV_NONHOST_CONFIG", + evNonhostInfo: "EV_NONHOST_INFO", + evOmitBootDeviceEvents: "EV_OMIT_BOOT_DEVICE_EVENTS", + + // UEFI events + evEFIVariableDriverConfig: "EV_EFI_VARIABLE_DRIVER_CONFIG", + evEFIVariableBoot: "EV_EFI_VARIABLE_BOOT", + evEFIBootServicesApplication: "EV_EFI_BOOT_SERVICES_APPLICATION", + evEFIBootServicesDriver: "EV_EFI_BOOT_SERVICES_DRIVER", + evEFIRuntimeServicesDriver: "EV_EFI_RUNTIME_SERVICES_DRIVER", + evEFIGPTEvent: "EV_EFI_GPT_EVENT", + evEFIAction: "EV_EFI_ACTION", + evEFIPlatformFirmwareBlob: "EV_EFI_PLATFORM_FIRMWARE_BLOB", + evEFIHandoffTables: "EV_EFI_HANDOFF_TABLES", + evEFIHCRTMEvent: "EV_EFI_HCRTM_EVENT", + evEFIVariableAuthority: "EV_EFI_VARIABLE_AUTHORITY", +} diff --git a/attest/attest-tool/internal/eventlog/secureboot.go b/attest/attest-tool/internal/eventlog/secureboot.go new file mode 100644 index 0000000..3f7ecb2 --- /dev/null +++ b/attest/attest-tool/internal/eventlog/secureboot.go @@ -0,0 +1,225 @@ +// Copyright 2019 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +package eventlog + +import ( + "bytes" + "crypto" + "encoding/binary" + "fmt" + "io" + "unicode/utf16" + + "github.com/google/go-attestation/attest" +) + +var ( + // https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=153 + efiGlobalVariable = efiGUID{ + 0x8BE4DF61, 0x93CA, 0x11d2, [8]uint8{0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C}} + + efiGlobalVariableSecureBoot = "SecureBoot" + efiGlobalVariablePlatformKey = "PK" + efiGlobalVariableKeyExchangeKey = "KEK" + + // https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1804 + efiImageSecurityDatabaseGUID = efiGUID{ + 0xd719b2cb, 0x3d3a, 0x4596, [8]uint8{0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f}} + + efiImageSecurityDatabase = "db" + efiImageSecurityDatabase1 = "dbx" + efiImageSecurityDatabase2 = "dbt" + efiImageSecurityDatabase3 = "dbr" +) + +type efiGUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]uint8 +} + +func (e efiGUID) String() string { + if s, ok := efiGUIDString[e]; ok { + return s + } + return fmt.Sprintf("{0x%x,0x%x,0x%x,{%x}}", e.Data1, e.Data2, e.Data3, e.Data4) +} + +var efiGUIDString = map[efiGUID]string{ + efiGlobalVariable: "EFI_GLOBAL_VARIABLE", + efiImageSecurityDatabaseGUID: "EFI_IMAGE_SECURITY_DATABASE_GUID", +} + +type uefiVariableData struct { + id efiGUID + name string + data []byte +} + +func (d *uefiVariableData) String() string { + return fmt.Sprintf("%s %s data length %d", d.id, d.name, len(d.data)) +} + +// SecureBoot holds parsed PCR 7 values representing secure boot settings for +// the device. +type SecureBoot struct { + Enabled bool + + // TODO(ericchiang): parse these as EFI_SIGNATURE_LIST + // + // https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1788 + + PK []byte + KEK []byte + + DB []byte + DBX []byte + + DBT []byte + DBR []byte + + // Authority is the set of certificate that were used during secure boot + // validation. This will be a subset of the certifiates in DB. + Authority []byte +} + +// ParseSecureBoot parses UEFI secure boot variables (PCR[7) from a verified event log. +// +// See https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=56 +func ParseSecureBoot(events []attest.Event) (*SecureBoot, error) { + var sb SecureBoot + seenSep := false + for i, e := range events { + if e.Index != 7 { + continue + } + t := eventType(e.Type) + switch t { + case evEFIVariableDriverConfig: + if seenSep { + return nil, fmt.Errorf("event %d %s after %s", i, t, evSeparator) + } + data, err := parseUEFIVariableData(e.Data, e.Digest) + if err != nil { + return nil, fmt.Errorf("parsing event %d, PCR[%02d] %s: %v", i, e.Index, t, err) + } + + switch data.id { + case efiGlobalVariable: + switch data.name { + case efiGlobalVariableSecureBoot: + if len(data.data) != 1 { + return nil, fmt.Errorf("%s/%s was %d bytes", data.id, data.name, len(data.data)) + } + switch data.data[0] { + case 0x0: + sb.Enabled = false + case 0x1: + sb.Enabled = true + default: + return nil, fmt.Errorf("invalid %s/%s value 0x%x", data.id, data.name, data.data) + } + case efiGlobalVariablePlatformKey: + sb.PK = data.data + case efiGlobalVariableKeyExchangeKey: + sb.KEK = data.data + } + case efiImageSecurityDatabaseGUID: + switch data.name { + case efiImageSecurityDatabase: + sb.DB = data.data + case efiImageSecurityDatabase1: + sb.DBX = data.data + case efiImageSecurityDatabase2: + sb.DBT = data.data + case efiImageSecurityDatabase3: + sb.DBR = data.data + } + } + case evEFIVariableAuthority: + if !seenSep { + return nil, fmt.Errorf("event %d %s before %s", i, t, evSeparator) + } + data, err := parseUEFIVariableData(e.Data, e.Digest) + if err != nil { + return nil, fmt.Errorf("parsing event %d, PCR[%02d] %s: %v", i, e.Index, t, err) + } + switch data.id { + case efiImageSecurityDatabaseGUID: + switch data.name { + case efiImageSecurityDatabase: + if !sb.Enabled { + return nil, fmt.Errorf("%s/%s present when secure boot wasn't enabled", t, data.name) + } + sb.Authority = data.data + } + } + case evSeparator: + seenSep = true + } + } + return &sb, nil +} + +func binaryRead(r io.Reader, i interface{}) error { + return binary.Read(r, binary.LittleEndian, i) +} + +var hashBySize = map[int]crypto.Hash{ + crypto.SHA1.Size(): crypto.SHA1, + crypto.SHA256.Size(): crypto.SHA256, +} + +func verifyDigest(digest, data []byte) bool { + h, ok := hashBySize[len(digest)] + if !ok { + return false + } + hash := h.New() + hash.Write(data) + return bytes.Equal(digest, hash.Sum(nil)) +} + +// parseUEFIVariableData parses a UEFI_VARIABLE_DATA struct and validates the +// digest of an event entry. +// +// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100 +func parseUEFIVariableData(b, digest []byte) (*uefiVariableData, error) { + r := bytes.NewBuffer(b) + var hdr struct { + ID efiGUID + NameLength uint64 + DataLength uint64 + } + if err := binaryRead(r, &hdr); err != nil { + return nil, err + } + name := make([]uint16, hdr.NameLength) + if err := binaryRead(r, &name); err != nil { + return nil, fmt.Errorf("parsing name: %v", err) + } + if r.Len() != int(hdr.DataLength) { + return nil, fmt.Errorf("remaining bytes %d doesn't match data length %d", r.Len(), hdr.DataLength) + } + data := r.Bytes() + // TODO(ericchiang): older UEFI firmware (Lenovo Bios version 1.17) logs the + // digest of the data, which doesn't encapsulate the ID or name. This lets + // attackers alter keys and we should determine if this is an acceptable risk. + if !verifyDigest(digest, b) && !verifyDigest(digest, data) { + return nil, fmt.Errorf("digest didn't match data") + } + return &uefiVariableData{id: hdr.ID, name: string(utf16.Decode(name)), data: r.Bytes()}, nil +} diff --git a/attest/attest-tool/internal/eventlog/secureboot_test.go b/attest/attest-tool/internal/eventlog/secureboot_test.go new file mode 100644 index 0000000..23d271c --- /dev/null +++ b/attest/attest-tool/internal/eventlog/secureboot_test.go @@ -0,0 +1,105 @@ +// Copyright 2019 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +package eventlog + +import ( + "encoding/json" + "io/ioutil" + "testing" + + "github.com/google/go-attestation/attest" + "github.com/google/go-attestation/attest/attest-tool/internal" +) + +func parseEvents(t *testing.T, testdata string) []attest.Event { + data, err := ioutil.ReadFile(testdata) + if err != nil { + t.Fatalf("reading test data: %v", err) + } + var dump internal.Dump + if err := json.Unmarshal(data, &dump); err != nil { + t.Fatalf("parsing test data: %v", err) + } + + aik, err := attest.ParseAIKPublic(dump.Static.TPMVersion, dump.AIK.Public) + if err != nil { + t.Fatalf("parsing AIK: %v", err) + } + if err := aik.Verify(attest.Quote{ + Version: dump.Static.TPMVersion, + Quote: dump.Quote.Quote, + Signature: dump.Quote.Signature, + }, dump.Log.PCRs, dump.Quote.Nonce); err != nil { + t.Fatalf("verifying quote: %v", err) + } + + el, err := attest.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) + } + return events +} + +func notEmpty(t *testing.T, name string, field []byte) { + t.Helper() + if len(field) == 0 { + t.Errorf("field %s wasn't populated", name) + } +} + +func isEmpty(t *testing.T, name string, field []byte) { + t.Helper() + if len(field) != 0 { + t.Errorf("expected field %s not to be populated", name) + } +} + +func TestParseSecureBootWindows(t *testing.T) { + events := parseEvents(t, "../../../testdata/windows_gcp_shielded_vm.json") + sb, err := ParseSecureBoot(events) + if err != nil { + t.Fatalf("parse secure boot: %v", err) + } + if !sb.Enabled { + t.Errorf("expected secure boot to be enabled") + } + notEmpty(t, "db", sb.DB) + notEmpty(t, "dbx", sb.DBX) + notEmpty(t, "pk", sb.PK) + notEmpty(t, "kek", sb.KEK) + isEmpty(t, "dbt", sb.DBT) + isEmpty(t, "dbr", sb.DBR) + notEmpty(t, "Authority", sb.Authority) +} + +func TestParseSecureBootLinux(t *testing.T) { + events := parseEvents(t, "../../../testdata/linux_tpm12.json") + sb, err := ParseSecureBoot(events) + if err != nil { + t.Errorf("parse secure boot: %v", err) + } + if sb.Enabled { + t.Errorf("expected secure boot to be disabled") + } + notEmpty(t, "db", sb.DB) + notEmpty(t, "dbx", sb.DBX) + isEmpty(t, "dbt", sb.DBT) + isEmpty(t, "dbr", sb.DBR) + isEmpty(t, "Authority", sb.Authority) +}