diff --git a/ccel/cceventlog_test.go b/ccel/cceventlog_test.go index 415d369..dc86151 100644 --- a/ccel/cceventlog_test.go +++ b/ccel/cceventlog_test.go @@ -27,6 +27,40 @@ import ( type eventLog struct { fname string mrs []register.MR + // TODO: migrate off of the slice based bank type and move to a map-based representation. + rtmrs []register.RTMR +} + +var COS113TDX = eventLog{ + fname: "../testdata/eventlogs/ccel/cos-113-intel-tdx.bin", + mrs: []register.MR{ + register.RTMR{ + Index: 0, + Digest: []byte("?\xa2\xf6\x1f9[\x7f_\xee\xfbN\xc2\xdfa)\x7f\x10\x9aث\xcdd\x10\xc1\xb7\xdf`\xf2\x1f7\xb1\x92\x97\xfc5\xe5D\x03\x9c~\x1e\xde\xceu*\xfd\x17\xf6"), + }, + register.RTMR{ + Index: 1, + Digest: []byte("\xf6-\xbc\a+\xd5\xd3\xf3C\x8b{5Úr\x7fZ\xea/\xfc$s\xf47#\x95?S\r\xafbPO\nyD\xaab\xc4\x1a\x86\xe8\xa8x±\"\xc1"), + }, + register.RTMR{ + Index: 2, + Digest: []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1"), + }, + }, + rtmrs: []register.RTMR{ + { + Index: 0, + Digest: []byte("?\xa2\xf6\x1f9[\x7f_\xee\xfbN\xc2\xdfa)\x7f\x10\x9aث\xcdd\x10\xc1\xb7\xdf`\xf2\x1f7\xb1\x92\x97\xfc5\xe5D\x03\x9c~\x1e\xde\xceu*\xfd\x17\xf6"), + }, + { + Index: 1, + Digest: []byte("\xf6-\xbc\a+\xd5\xd3\xf3C\x8b{5Úr\x7fZ\xea/\xfc$s\xf47#\x95?S\r\xafbPO\nyD\xaab\xc4\x1a\x86\xe8\xa8x±\"\xc1"), + }, + { + Index: 2, + Digest: []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1"), + }, + }, } var COS113TDXUnpadded = eventLog{ @@ -45,6 +79,20 @@ var COS113TDXUnpadded = eventLog{ Digest: []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1"), }, }, + rtmrs: []register.RTMR{ + { + Index: 0, + Digest: []byte("\xa4\xde-\xf2>\x96\x11)\x91#\xbaCY\xc4*^W\x8b\x0f\x84\x88\xbf\x1b\xba\x8e\xf5`m\x9e\xa5\xd8\x1c\x97\xc0d\xb4\x82\xa5\xea\xc57\xd1f\xbd\x0f\x0fu-"), + }, + { + Index: 1, + Digest: []byte("\x0e\xe96l\x92\x8aw\t/U\xe9\xe1\x14\xc79A\x81\xfd&F\x99\x15_\r\xf7}#Wv\x18\xd5\xf6PV\x8a\x17\xd3y5Z\a\xbd\x84nU/N "), + }, + { + Index: 2, + Digest: []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1"), + }, + }, } var COS113TDXPadded = eventLog{ @@ -63,6 +111,20 @@ var COS113TDXPadded = eventLog{ Digest: []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1"), }, }, + rtmrs: []register.RTMR{ + { + Index: 0, + Digest: []byte("\xa4\xde-\xf2>\x96\x11)\x91#\xbaCY\xc4*^W\x8b\x0f\x84\x88\xbf\x1b\xba\x8e\xf5`m\x9e\xa5\xd8\x1c\x97\xc0d\xb4\x82\xa5\xea\xc57\xd1f\xbd\x0f\x0fu-"), + }, + { + Index: 1, + Digest: []byte("\x0e\xe96l\x92\x8aw\t/U\xe9\xe1\x14\xc79A\x81\xfd&F\x99\x15_\r\xf7}#Wv\x18\xd5\xf6PV\x8a\x17\xd3y5Z\a\xbd\x84nU/N "), + }, + { + Index: 2, + Digest: []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1"), + }, + }, } var IntelTestCCEL = eventLog{ @@ -81,6 +143,46 @@ var IntelTestCCEL = eventLog{ Digest: []byte("\x80\x83\xcdh\x98\xccR\xa9\x021\xcd\xf9\xc0S+\xf9Q<@F\\oq\xe5l\xbe2\xee,\x11\xa9\xdf\xc00)|\xa3\xca\x0fbG}m\x1fa\r?\xdb"), }, }, + rtmrs: []register.RTMR{ + { + Index: 0, + Digest: []byte("\x80\x83\xcdh\x98\xccR\xa9\x021\xcd\xf9\xc0S+\xf9Q<@F\\oq\xe5l\xbe2\xee,\x11\xa9\xdf\xc00)|\xa3\xca\x0fbG}m\x1fa\r?\xdb"), + }, + { + Index: 1, + Digest: []byte("\x80\x83\xcdh\x98\xccR\xa9\x021\xcd\xf9\xc0S+\xf9Q<@F\\oq\xe5l\xbe2\xee,\x11\xa9\xdf\xc00)|\xa3\xca\x0fbG}m\x1fa\r?\xdb"), + }, + { + Index: 2, + Digest: []byte("\x80\x83\xcdh\x98\xccR\xa9\x021\xcd\xf9\xc0S+\xf9Q<@F\\oq\xe5l\xbe2\xee,\x11\xa9\xdf\xc00)|\xa3\xca\x0fbG}m\x1fa\r?\xdb"), + }, + }, +} + +var GDCCCEL = eventLog{ + fname: "../testdata/eventlogs/ccel/gdc-tdx.bin", + mrs: []register.MR{ + register.RTMR{ + Index: 0, + Digest: []byte("FU\xef\x03\xc8w\xb3\xd7Jf >F\x85\x8f\xb9\x90۩t\xa4\\\xa6P\x85\xbcFE\x943n\x04\xebI\xca\x10\x0ej\x1c\xeb\xe7\xae2/2\x88\xb0\x8f")}, + register.RTMR{ + Index: 1, + Digest: []byte("\xbf\x86\xaa\xc1@\xc1\x05\a\xb7<#\xd2\xf3\xa6v\xb6\xa3iZ\x9a\xad\xe3c5s1\x80\xb0K\x0e\xec\xd2\r\x05\xab\xe2\xe3\xaa^\x8b\v\xads\xfa\xe3\x0f4\xf4")}, + register.RTMR{ + Index: 2, + Digest: []byte("\xb6_\x82\x02\xd0\xd3\xc9g\x9f\xe0\xb1\xf3\xf3A\xa5\xc8\ue91e\xa4\x93\x14d\x16\xde\xed\x8a\xe3c\xd7c%D\xd4)BN* \x824\xc7n\xd5\xc1\xba\t\xce")}, + }, + rtmrs: []register.RTMR{ + { + Index: 0, + Digest: []byte("FU\xef\x03\xc8w\xb3\xd7Jf >F\x85\x8f\xb9\x90۩t\xa4\\\xa6P\x85\xbcFE\x943n\x04\xebI\xca\x10\x0ej\x1c\xeb\xe7\xae2/2\x88\xb0\x8f")}, + { + Index: 1, + Digest: []byte("\xbf\x86\xaa\xc1@\xc1\x05\a\xb7<#\xd2\xf3\xa6v\xb6\xa3iZ\x9a\xad\xe3c5s1\x80\xb0K\x0e\xec\xd2\r\x05\xab\xe2\xe3\xaa^\x8b\v\xads\xfa\xe3\x0f4\xf4")}, + { + Index: 2, + Digest: []byte("\xb6_\x82\x02\xd0\xd3\xc9g\x9f\xe0\xb1\xf3\xf3A\xa5\xc8\ue91e\xa4\x93\x14d\x16\xde\xed\x8a\xe3c\xd7c%D\xd4)BN* \x824\xc7n\xd5\xc1\xba\t\xce")}, + }, } func TestParseAndReplay(t *testing.T) { @@ -109,6 +211,16 @@ func TestParseAndReplay(t *testing.T) { allowPadding: false, wantErr: true, }, + { + el: GDCCCEL, + allowPadding: true, + wantErr: false, + }, + { + el: GDCCCEL, + allowPadding: false, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.el.fname+"_allowPadding_"+strconv.FormatBool(tt.allowPadding), func(t *testing.T) { diff --git a/ccel/replay_test.go b/ccel/replay_test.go index 337a3c0..f07af27 100644 --- a/ccel/replay_test.go +++ b/ccel/replay_test.go @@ -16,6 +16,7 @@ package ccel import ( "os" + "strconv" "strings" "testing" @@ -24,26 +25,41 @@ import ( ) func TestReplayAndExtract(t *testing.T) { - elBytes, err := os.ReadFile("../testdata/eventlogs/ccel/cos-113-intel-tdx.bin") - if err != nil { - t.Fatal(err) - } tableBytes, err := os.ReadFile("../testdata/eventlogs/ccel/cos-113-intel-tdx.table.bin") if err != nil { t.Fatal(err) } + tests := []struct { + el eventLog + opts extract.Opts + wantErr bool + }{ + { + el: COS113TDX, + opts: extract.Opts{Loader: extract.GRUB}, + }, + { + el: GDCCCEL, + opts: extract.Opts{Loader: extract.GRUB, AllowEmptySBVar: true}, + }, + { + el: GDCCCEL, + opts: extract.Opts{Loader: extract.GRUB}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.el.fname+strconv.FormatBool(tt.wantErr), func(t *testing.T) { + elBytes, err := os.ReadFile(tt.el.fname) + if err != nil { + t.Fatal(err) + } - rtmr0 := []byte("?\xa2\xf6\x1f9[\x7f_\xee\xfbN\xc2\xdfa)\x7f\x10\x9aث\xcdd\x10\xc1\xb7\xdf`\xf2\x1f7\xb1\x92\x97\xfc5\xe5D\x03\x9c~\x1e\xde\xceu*\xfd\x17\xf6") - rtmr1 := []byte("\xf6-\xbc\a+\xd5\xd3\xf3C\x8b{5Úr\x7fZ\xea/\xfc$s\xf47#\x95?S\r\xafbPO\nyD\xaab\xc4\x1a\x86\xe8\xa8x±\"\xc1") - rtmr2 := []byte("IihM\xc8s\x81\xfc;14\x17l\x8d\x88\x06\xea\xf0\xa9\x01\x85\x9f_pϮ\x8d\x17qKF\xc1\n\x8d\xe2\x19\x04\x8c\x9f\xc0\x9f\x11\xf3\x81\xa6\xfb\xe7\xc1") - bank := register.RTMRBank{RTMRs: []register.RTMR{ - {Index: 0, Digest: rtmr0}, - {Index: 1, Digest: rtmr1}, - {Index: 2, Digest: rtmr2}, - }} - _, err = ReplayAndExtract(tableBytes, elBytes, bank, extract.Opts{Loader: extract.GRUB}) - if err != nil { - t.Errorf("failed to ReplayAndExtract from CCEL: %v", err) + _, err = ReplayAndExtract(tableBytes, elBytes, register.RTMRBank{RTMRs: tt.el.rtmrs}, tt.opts) + if (err != nil) != tt.wantErr { + t.Errorf("ReplayAndExtract: got %v, wantErr %v", err, tt.wantErr) + } + }) } } diff --git a/extract/extract.go b/extract/extract.go index 31d9985..7246d86 100644 --- a/extract/extract.go +++ b/extract/extract.go @@ -57,6 +57,9 @@ const ( // Opts gives options for extracting information from an event log. type Opts struct { Loader Bootloader + // AllowEmptySBVar allows the SecureBoot variable to be empty in addition to length 1 (0 or 1). + // This can be used when the SecureBoot variable is not initialized. + AllowEmptySBVar bool } // FirmwareLogState extracts event info from a verified TCG PC Client event @@ -81,7 +84,7 @@ func FirmwareLogState(events []tcg.Event, hash crypto.Hash, registerCfg register if err != nil { joined = errors.Join(joined, err) } - sbState, err := SecureBootState(events, registerCfg) + sbState, err := SecureBootState(events, registerCfg, opts) if err != nil { joined = errors.Join(joined, err) } @@ -209,8 +212,8 @@ func matchWellKnown(cert x509.Certificate) (pb.WellKnownCertificate, error) { // SecureBootState extracts Secure Boot information from a UEFI TCG2 // firmware event log. -func SecureBootState(replayEvents []tcg.Event, registerCfg registerConfig) (*pb.SecureBootState, error) { - attestSbState, err := ParseSecurebootState(replayEvents, registerCfg) +func SecureBootState(replayEvents []tcg.Event, registerCfg registerConfig, opts Opts) (*pb.SecureBootState, error) { + attestSbState, err := ParseSecurebootState(replayEvents, registerCfg, opts) if err != nil { return nil, fmt.Errorf("failed to parse SecureBootState: %v", err) } diff --git a/extract/secureboot.go b/extract/secureboot.go index b0f28da..48ca162 100644 --- a/extract/secureboot.go +++ b/extract/secureboot.go @@ -97,7 +97,7 @@ func ParseSecurebootStateLegacy(events []tcg.Event) (*SecurebootState, error) { // - If SecureBoot was 1 (enabled), platform + exchange + database keys // were specified. // - No UEFI debugger was attached. - return ParseSecurebootState(events, TPMRegisterConfig) + return ParseSecurebootState(events, TPMRegisterConfig, Opts{}) } // ParseSecurebootState parses a series of events to determine the @@ -105,7 +105,7 @@ func ParseSecurebootStateLegacy(events []tcg.Event) (*SecurebootState, error) { // 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 []tcg.Event, registerCfg registerConfig) (*SecurebootState, error) { +func ParseSecurebootState(events []tcg.Event, registerCfg registerConfig, opts Opts) (*SecurebootState, error) { var ( out SecurebootState seenSeparator7 bool @@ -173,10 +173,14 @@ func ParseSecurebootState(events []tcg.Event, registerCfg registerConfig) (*Secu switch v.VarName() { case "SecureBoot": - if len(v.VariableData) != 1 { + if len(v.VariableData) == 1 { + out.Enabled = v.VariableData[0] == 1 + } else if len(v.VariableData) == 0 && opts.AllowEmptySBVar { + out.Enabled = false + } else { return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.Num(), 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.Num(), err) diff --git a/extract/secureboot_test.go b/extract/secureboot_test.go index 7f86233..63c8ab7 100644 --- a/extract/secureboot_test.go +++ b/extract/secureboot_test.go @@ -15,12 +15,15 @@ package extract_test import ( + "bytes" "crypto" + "crypto/sha256" "encoding/base64" "encoding/json" "os" "testing" + "github.com/google/go-cmp/cmp" "github.com/google/go-eventlog/extract" "github.com/google/go-eventlog/internal/testutil" "github.com/google/go-eventlog/proto/state" @@ -50,7 +53,7 @@ func TestSecureBoot(t *testing.T) { t.Fatalf("validating event log: %v", err) } - sbState, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig) + sbState, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig, extract.Opts{}) if err != nil { t.Fatalf("ExtractSecurebootState() failed: %v", err) } @@ -127,7 +130,7 @@ func TestSecureBootBug157(t *testing.T) { t.Fatalf("failed to verify log: %v", err) } - sbs, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig) + sbs, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig, extract.Opts{}) if err != nil { t.Fatalf("failed parsing secureboot state: %v", err) } @@ -170,7 +173,7 @@ func TestSecureBootOptionRom(t *testing.T) { t.Errorf("failed to verify log: %v", err) } - sbs, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig) + sbs, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig, extract.Opts{}) if err != nil { t.Errorf("failed parsing secureboot state: %v", err) } @@ -199,7 +202,7 @@ func TestSecureBootEventLogUbuntu(t *testing.T) { if err != nil { t.Fatalf("verifying event log: %v", err) } - _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig) + _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig, extract.Opts{}) if err != nil { t.Errorf("parsing sb state: %v", err) } @@ -218,8 +221,139 @@ func TestSecureBootEventLogFedora36(t *testing.T) { if err != nil { t.Fatalf("verifying event log: %v", err) } - _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig) + _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig, extract.Opts{}) if err != nil { t.Errorf("parsing sb state: %v", err) } } + +func TestEncodeUEFIVariableData(t *testing.T) { + data, err := os.ReadFile("../testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog") + if err != nil { + t.Fatalf("reading test data: %v", err) + } + el, err := tcg.ParseEventLog(data, tcg.ParseOpts{}) + if err != nil { + t.Fatalf("parsing event log: %v", err) + } + evts := el.Events(register.HashSHA256) + if err != nil { + t.Fatalf("verifying event log: %v", err) + } + for _, evt := range evts { + if evt.Type != tcg.EFIVariableDriverConfig { + continue + } + v, err := tcg.ParseUEFIVariableData(bytes.NewReader(evt.RawData())) + if err != nil { + t.Fatal(err) + } + data, err := v.Encode() + if err != nil { + t.Fatal(err) + } + newv, err := tcg.ParseUEFIVariableData(bytes.NewReader(data)) + if err != nil { + t.Errorf("failed to parse after encoding: %v", err) + } + if diff := cmp.Diff(v, newv); diff != "" { + t.Errorf("Encode() produced different encodings: %v", diff) + } + } + +} + +func TestSecureBootAllowEmptySBVar(t *testing.T) { + data, err := os.ReadFile("../testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog") + if err != nil { + t.Fatalf("reading test data: %v", err) + } + el, err := tcg.ParseEventLog(data, tcg.ParseOpts{}) + if err != nil { + t.Fatalf("parsing event log: %v", err) + } + evts := el.Events(register.HashSHA256) + if err != nil { + t.Fatalf("verifying event log: %v", err) + } + tests := []struct { + name string + newVar []byte + allowEmpty bool + wantErr bool + }{ + { + name: "emptyAllowed", + allowEmpty: true, + }, + { + name: "emptyNotAllowed", + wantErr: true, + }, + { + name: "1emptyAllowed", + newVar: []byte{1}, + allowEmpty: true, + }, + { + name: "1emptyNotAllowed", + newVar: []byte{1}, + }, + { + name: "0emptyAllowed", + newVar: []byte{0}, + allowEmpty: true, + }, + { + name: "0emptyNotAllowed", + newVar: []byte{0}, + }, + { + name: "len2emptyAllowed", + newVar: []byte{0, 1}, + allowEmpty: true, + wantErr: true, + }, + { + name: "len2emptyNotAllowed", + newVar: []byte{0, 1}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for i, evt := range evts { + if evt.Type != tcg.EFIVariableDriverConfig { + continue + } + v, err := tcg.ParseUEFIVariableData(bytes.NewReader(evt.RawData())) + if err != nil { + t.Fatal(err) + } + if v.VarName() == "SecureBoot" { + v.VariableData = tt.newVar + } + data, err := v.Encode() + if err != nil { + t.Fatal(err) + } + evt.Data = data + dgst := sha256.Sum256(evt.Data) + evt.Digest = dgst[:] + evts[i] = evt + } + opts := extract.Opts{} + if tt.allowEmpty { + opts.AllowEmptySBVar = true + } + _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig, opts) + if (err != nil) != tt.wantErr { + t.Errorf("ParseSecurebootState() = %v, wantErr %v", err, tt.wantErr) + + } + + }) + } + +} diff --git a/legacy/secureboot.go.bak b/legacy/secureboot.go.bak deleted file mode 100644 index 004dfb8..0000000 --- a/legacy/secureboot.go.bak +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2024 Google LLC -// -// 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 legacy - -import ( - "bytes" - "crypto/x509" - "errors" - "fmt" - - "github.com/google/go-eventlog/tcg" - "github.com/google/go-eventlog/tpmeventlog" -) - -// 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 - - // DriverLoadSourceHints describes the origin of boot services drivers. - // This data is not tamper-proof and must only be used as a hint. - DriverLoadSourceHints []DriverLoadSource - - // DMAProtectionDisabled is true if the platform reports during boot that - // DMA protection is supported but disabled. - // - // See: https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-kernel-dma-protection - DMAProtectionDisabled bool -} - -// DriverLoadSource describes the logical origin of a boot services driver. -type DriverLoadSource uint8 - -const ( - UnknownSource DriverLoadSource = iota - PciMmioSource -) - -// 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 []tpmeventlog.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 - seenSeparator7 bool - seenSeparator2 bool - seenAuthority bool - seenVars = map[string]bool{} - driverSources [][]tcg.EFIDevicePathElement - ) - - for _, e := range events { - if e.Index != 7 && e.Index != 2 { - continue - } - - et, err := tcg.UntrustedParseEventType(uint32(e.Type)) - if err != nil { - return nil, fmt.Errorf("unrecognised event type: %v", err) - } - digestVerify := DigestEquals(&e, e.Data) - - switch e.Index { - case 7: - switch et { - case tcg.Separator: - if seenSeparator7 { - return nil, fmt.Errorf("duplicate separator at event %d", e.Sequence) - } - seenSeparator7 = 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 tcg.EFIAction: - switch string(e.Data) { - case "UEFI Debug Mode": - return nil, errors.New("a UEFI debugger was present during boot") - case "DMA Protection Disabled": - if digestVerify != nil { - return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.Sequence, digestVerify) - } - out.DMAProtectionDisabled = true - default: - return nil, fmt.Errorf("event %d: unexpected EFI action event", e.Sequence) - } - - case tcg.EFIVariableDriverConfig: - v, err := tcg.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 seenSeparator7 { - 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 tcg.EFIVariableAuthority: - v, err := tcg.ParseUEFIVariableData(bytes.NewReader(e.Data)) - if err != nil { - return nil, fmt.Errorf("failed parsing UEFI variable data: %v", err) - } - - a, err := tcg.ParseUEFIVariableAuthority(v) - if err != nil { - // Workaround for: https://github.com/google/go-attestation/issues/157 - if err == tcg.ErrSigMissingGUID { - // Versions of shim which do not carry - // https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50 - // have an erroneous additional byte in the event, which breaks digest - // verification. If verification failed, we try removing the last byte. - if digestVerify != nil && len(e.Data) > 0 { - digestVerify = DigestEquals(&e, e.Data[:len(e.Data)-1]) - } - } else { - 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 !seenSeparator7 { - out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...) - } else { - out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...) - } - - default: - return nil, fmt.Errorf("unexpected event type in PCR7: %v", et) - } - - case 2: - switch et { - case tcg.Separator: - if seenSeparator2 { - return nil, fmt.Errorf("duplicate separator at event %d", e.Sequence) - } - seenSeparator2 = 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 tcg.EFIBootServicesDriver: - if !seenSeparator2 { - imgLoad, err := tcg.ParseEFIImageLoad(bytes.NewReader(e.Data)) - if err != nil { - return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.Sequence, err) - } - dp, err := imgLoad.DevicePath() - if err != nil { - return nil, fmt.Errorf("failed to parse device path for driver load event %d: %v", e.Sequence, err) - } - driverSources = append(driverSources, dp) - } - } - } - } - - // Compute driver source hints based on the EFI device path observed in - // EFI Boot-services driver-load events. -sourceLoop: - for _, source := range driverSources { - // We consider a driver to have originated from PCI-MMIO if any number - // of elements in the device path [1] were PCI devices, and are followed by - // an element representing a "relative offset range" read. - // In the wild, we have typically observed 4-tuple device paths for such - // devices: ACPI device -> PCI device -> PCI device -> relative offset. - // - // [1]: See section 9 of the UEFI specification v2.6 or greater. - var seenPCI bool - for _, e := range source { - // subtype 0x1 corresponds to a PCI device (See: 9.3.2.1) - if e.Type == tcg.HardwareDevice && e.Subtype == 0x1 { - seenPCI = true - } - // subtype 0x8 corresponds to "relative offset range" (See: 9.3.6.8) - if seenPCI && e.Type == tcg.MediaDevice && e.Subtype == 0x8 { - out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, PciMmioSource) - continue sourceLoop - } - } - out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, UnknownSource) - } - - 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 -} diff --git a/tcg/events.go b/tcg/events.go index a2ab16d..abe41e6 100644 --- a/tcg/events.go +++ b/tcg/events.go @@ -314,6 +314,33 @@ type UEFIVariableData struct { VariableData []byte // []int8 } +// Encode encodes the UEFIVariableData struct into raw bytes. +func (v *UEFIVariableData) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + + v.Header.UnicodeNameLength = uint64(len(v.UnicodeName)) + v.Header.VariableDataLength = uint64(len(v.VariableData)) + + err := binary.Write(buf, binary.LittleEndian, v.Header) + if err != nil { + return nil, fmt.Errorf("error encoding header: %w", err) + } + + for _, nameChar := range v.UnicodeName { + err = binary.Write(buf, binary.LittleEndian, nameChar) + if err != nil { + return nil, fmt.Errorf("error encoding unicode name: %w", err) + } + } + + _, err = buf.Write(v.VariableData) + if err != nil { + return nil, fmt.Errorf("error encoding variable data: %w", err) + } + + return buf.Bytes(), nil +} + // ParseUEFIVariableData parses the data section of an event structured as // a UEFI variable. // diff --git a/testdata/eventlogs/ccel/gdc-tdx.bin b/testdata/eventlogs/ccel/gdc-tdx.bin new file mode 100644 index 0000000..c5e544d Binary files /dev/null and b/testdata/eventlogs/ccel/gdc-tdx.bin differ