From 9021153e898d2982a2c82ccbc8be506264b07211 Mon Sep 17 00:00:00 2001
From: Eric Chiang <ericchiang@google.com>
Date: Wed, 4 Sep 2019 14:19:54 -0700
Subject: [PATCH] internal/eventlog: add code for parsing secure boot variables

This is being prototyped in an internal package as we start to open
source. This code will either live in attest, or in a separate eventlog
package in the future.
---
 .../attest-tool/internal/eventlog/eventlog.go | 185 ++++++++++++++
 .../internal/eventlog/secureboot.go           | 225 ++++++++++++++++++
 .../internal/eventlog/secureboot_test.go      | 105 ++++++++
 3 files changed, 515 insertions(+)
 create mode 100644 attest/attest-tool/internal/eventlog/eventlog.go
 create mode 100644 attest/attest-tool/internal/eventlog/secureboot.go
 create mode 100644 attest/attest-tool/internal/eventlog/secureboot_test.go

diff --git a/attest/attest-tool/internal/eventlog/eventlog.go b/attest/attest-tool/internal/eventlog/eventlog.go
new file mode 100644
index 00000000..b85797d3
--- /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 00000000..3f7ecb29
--- /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 00000000..23d271ca
--- /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)
+}