├── testdata ├── legacydata │ ├── short_no_action_eventlog │ ├── sb_cert_eventlog │ ├── option_rom_eventlog │ ├── crypto_agile_eventlog │ ├── ebs_event_missing_eventlog │ ├── coreos_36_shielded_vm_no_secure_boot_eventlog │ └── ubuntu_2104_shielded_vm_no_secure_boot_eventlog ├── eventlogs │ ├── ccel │ │ ├── CCEL.bin │ │ ├── event_log.bin │ │ ├── gdc-tdx.bin │ │ ├── CCEL_unpadded.data.bin │ │ ├── cos-113-intel-tdx.bin │ │ ├── cos-113-intel-tdx.table.bin │ │ ├── cos-113-intel-tdx-dupe-separator.bin │ │ └── cos-113-intel-tdx-dupe-separator-unpadded.bin │ └── tpm │ │ ├── debian-10.bin │ │ ├── gdc-host.bin │ │ ├── rhel8-uefi.bin │ │ ├── glinux-alex.bin │ │ ├── cos-101-amd-sev.bin │ │ ├── cos-121-amd-sev.bin │ │ ├── cos-85-amd-sev.bin │ │ ├── cos-93-amd-sev.bin │ │ ├── ubuntu-1804-amd-sev.bin │ │ ├── ubuntu-2104-no-dbx.bin │ │ ├── arch-linux-workstation.bin │ │ ├── ubuntu-2404-amd-sevsnp.bin │ │ └── ubuntu-2104-no-secure-boot.bin └── eventlog_data.go ├── wellknown ├── secure-boot │ ├── GcePk.crt │ ├── cisco-boothole.crt │ ├── debian-boothole.crt │ ├── canonical-boothole.crt │ ├── dbxupdate-2014-08-11.bin │ ├── MicCorKEKCA2011_2011-06-24.crt │ ├── MicCorUEFCA2011_2011-06-27.crt │ ├── dbxupdate_x64-2020-10-12.bin │ ├── dbxupdate_x64-2021-04-29.bin │ └── MicWinProPCA2011_2011-10-19.crt └── policy_constants.go ├── .gitignore ├── cel ├── README.md ├── fake_tlv.go ├── canonical_eventlog_test.go └── canonical_eventlog.go ├── ccel ├── README.md ├── replay.go ├── cceventlog.go ├── replay_test.go └── cceventlog_test.go ├── go.mod ├── go.sum ├── tpmeventlog ├── tpmeventlog_fuzz.go └── replay.go ├── internal └── testutil │ ├── dump.go │ └── testutil.go ├── register ├── measurement_register.go ├── fake_mr.go ├── rtmr.go ├── fake_rot.go └── pcr.go ├── proto ├── state │ └── state_extension.go ├── doc.go └── state.proto ├── CONTRIBUTING.md ├── legacy └── legacyevent.go ├── extract ├── util_test.go ├── rtmr.go ├── util.go ├── pcr.go ├── registerconfig.go ├── secureboot.go ├── extract_test.go ├── secureboot_test.go └── extract.go ├── tcg ├── eventlog_workarounds.go └── eventlog_test.go ├── .github └── workflows │ └── ci.yml ├── README.md └── LICENSE /testdata/legacydata/short_no_action_eventlog: -------------------------------------------------------------------------------- 1 | StartupLocality -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/CCEL.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/CCEL.bin -------------------------------------------------------------------------------- /wellknown/secure-boot/GcePk.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/GcePk.crt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | *.test.exe 3 | *.pkg.tar.xz 4 | .vscode* 5 | *.code-workspace 6 | main 7 | go.work 8 | go.work.sum 9 | -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/event_log.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/event_log.bin -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/gdc-tdx.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/gdc-tdx.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/debian-10.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/debian-10.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/gdc-host.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/gdc-host.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/rhel8-uefi.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/rhel8-uefi.bin -------------------------------------------------------------------------------- /testdata/legacydata/sb_cert_eventlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/legacydata/sb_cert_eventlog -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/glinux-alex.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/glinux-alex.bin -------------------------------------------------------------------------------- /testdata/legacydata/option_rom_eventlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/legacydata/option_rom_eventlog -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/cos-101-amd-sev.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/cos-101-amd-sev.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/cos-121-amd-sev.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/cos-121-amd-sev.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/cos-85-amd-sev.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/cos-85-amd-sev.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/cos-93-amd-sev.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/cos-93-amd-sev.bin -------------------------------------------------------------------------------- /testdata/legacydata/crypto_agile_eventlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/legacydata/crypto_agile_eventlog -------------------------------------------------------------------------------- /wellknown/secure-boot/cisco-boothole.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/cisco-boothole.crt -------------------------------------------------------------------------------- /wellknown/secure-boot/debian-boothole.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/debian-boothole.crt -------------------------------------------------------------------------------- /wellknown/secure-boot/canonical-boothole.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/canonical-boothole.crt -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/CCEL_unpadded.data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/CCEL_unpadded.data.bin -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/cos-113-intel-tdx.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/cos-113-intel-tdx.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/ubuntu-1804-amd-sev.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/ubuntu-1804-amd-sev.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/ubuntu-2104-no-dbx.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/ubuntu-2104-no-dbx.bin -------------------------------------------------------------------------------- /testdata/legacydata/ebs_event_missing_eventlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/legacydata/ebs_event_missing_eventlog -------------------------------------------------------------------------------- /wellknown/secure-boot/dbxupdate-2014-08-11.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/dbxupdate-2014-08-11.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/arch-linux-workstation.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/arch-linux-workstation.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/ubuntu-2404-amd-sevsnp.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/ubuntu-2404-amd-sevsnp.bin -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/cos-113-intel-tdx.table.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/cos-113-intel-tdx.table.bin -------------------------------------------------------------------------------- /wellknown/secure-boot/MicCorKEKCA2011_2011-06-24.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/MicCorKEKCA2011_2011-06-24.crt -------------------------------------------------------------------------------- /wellknown/secure-boot/MicCorUEFCA2011_2011-06-27.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/MicCorUEFCA2011_2011-06-27.crt -------------------------------------------------------------------------------- /wellknown/secure-boot/dbxupdate_x64-2020-10-12.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/dbxupdate_x64-2020-10-12.bin -------------------------------------------------------------------------------- /wellknown/secure-boot/dbxupdate_x64-2021-04-29.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/dbxupdate_x64-2021-04-29.bin -------------------------------------------------------------------------------- /testdata/eventlogs/tpm/ubuntu-2104-no-secure-boot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/tpm/ubuntu-2104-no-secure-boot.bin -------------------------------------------------------------------------------- /wellknown/secure-boot/MicWinProPCA2011_2011-10-19.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/wellknown/secure-boot/MicWinProPCA2011_2011-10-19.crt -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator.bin -------------------------------------------------------------------------------- /testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog -------------------------------------------------------------------------------- /testdata/legacydata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/legacydata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog -------------------------------------------------------------------------------- /testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator-unpadded.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/go-eventlog/HEAD/testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator-unpadded.bin -------------------------------------------------------------------------------- /cel/README.md: -------------------------------------------------------------------------------- 1 | # Canonical Event Log Format (CEL) 2 | 3 | See https://trustedcomputinggroup.org/resource/canonical-event-log-format/. 4 | 5 | Not to be confused with Confidential Computing Event Log (CCEL). -------------------------------------------------------------------------------- /ccel/README.md: -------------------------------------------------------------------------------- 1 | # Confidential Compute Event Log (CCEL) 2 | 3 | See https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#cc-event-log-acpi-table. 4 | 5 | Not to be confused with Canonical Event Log (CEL). -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/google/go-eventlog 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/go-cmp v0.6.0 7 | github.com/google/go-tpm v0.9.0 8 | google.golang.org/protobuf v1.34.2 9 | ) 10 | 11 | require golang.org/x/sys v0.19.0 // indirect 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= 4 | github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= 5 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 6 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 7 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 8 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 9 | -------------------------------------------------------------------------------- /tpmeventlog/tpmeventlog_fuzz.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | //go:build gofuzz 16 | // +build gofuzz 17 | 18 | package tpmeventlog 19 | 20 | // FuzzParseEventLog is an exported entrypoint for fuzzers to test the eventlog 21 | // parser. This method should not be used for any other purpose. 22 | func FuzzParseEventLog(data []byte) int { 23 | _, err := ParseEventLog(data) 24 | if err != nil { 25 | return 0 26 | } 27 | return 1 28 | } 29 | -------------------------------------------------------------------------------- /internal/testutil/dump.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package testutil includes utilities for event log tests. 16 | package testutil 17 | 18 | import ( 19 | "github.com/google/go-eventlog/register" 20 | "github.com/google/go-tpm/legacy/tpm2" 21 | ) 22 | 23 | // Dump describes the layout of serialized information from the dump command. 24 | type Dump struct { 25 | Log struct { 26 | PCRs []register.PCR 27 | PCRAlg tpm2.Algorithm 28 | Raw []byte // The measured boot log in binary form. 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /register/measurement_register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package register 16 | 17 | import ( 18 | "crypto" 19 | ) 20 | 21 | // MRBank is a generic interface for a collection of measurement registers 22 | // associated with the same hash algorithm. 23 | type MRBank interface { 24 | CryptoHash() (crypto.Hash, error) 25 | MRs() []MR 26 | } 27 | 28 | // MR provides a generic interface for measurement registers to implement. 29 | type MR interface { 30 | Idx() int 31 | Dgst() []byte 32 | DgstAlg() crypto.Hash 33 | } 34 | -------------------------------------------------------------------------------- /proto/state/state_extension.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package state contains the definitions and utilities related to extracting 16 | // information from an event log. 17 | package state 18 | 19 | import ( 20 | "crypto" 21 | 22 | "github.com/google/go-tpm/legacy/tpm2" 23 | ) 24 | 25 | // CryptoHash converts the TCG registry hash identifier to a crypto.Hash. 26 | func (ha HashAlgo) CryptoHash() (crypto.Hash, error) { 27 | tcgHash := tpm2.Algorithm(uint16(ha)) 28 | cryptoHash, err := tcgHash.Hash() 29 | if err != nil { 30 | return crypto.Hash(0), err 31 | } 32 | return cryptoHash, nil 33 | } 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). 29 | 30 | ## Releasing a new version 31 | 32 | See [`RELEASING.md`](RELEASING.md) for instructions on how to cut a new 33 | version of go-tpm-tools. 34 | -------------------------------------------------------------------------------- /internal/testutil/testutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package testutil 16 | 17 | import ( 18 | pb "github.com/google/go-eventlog/proto/state" 19 | "github.com/google/go-eventlog/register" 20 | ) 21 | 22 | // MakePCRBank takes a hash and a map of index to digest and creates the 23 | // corresponding PCRBank. 24 | func MakePCRBank(hashAlgo pb.HashAlgo, pcrIdxToDigest map[uint32][]byte) register.PCRBank { 25 | pcrs := make([]register.PCR, 0, len(pcrIdxToDigest)) 26 | digestAlg, err := hashAlgo.CryptoHash() 27 | if err != nil { 28 | panic(err) 29 | } 30 | for pcrIdx, digest := range pcrIdxToDigest { 31 | pcrs = append(pcrs, register.PCR{ 32 | Index: int(pcrIdx), 33 | Digest: digest, 34 | DigestAlg: digestAlg, 35 | }) 36 | } 37 | return register.PCRBank{ 38 | TCGHashAlgo: hashAlgo, 39 | PCRs: pcrs, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /register/fake_mr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package register 16 | 17 | import ( 18 | "crypto" 19 | ) 20 | 21 | // FakeMRBank is a bank of FakeMRs that all correspond to the same hash algorithm. 22 | type FakeMRBank struct { 23 | Hash crypto.Hash 24 | FakeMRs []FakeMR 25 | } 26 | 27 | // CryptoHash returns the crypto.Hash algorithm related to the FakeMR bank. 28 | func (f FakeMRBank) CryptoHash() (crypto.Hash, error) { 29 | return f.Hash, nil 30 | } 31 | 32 | // MRs returns a slice of MR from the PCR implementation. 33 | func (f FakeMRBank) MRs() []MR { 34 | mrs := make([]MR, len(f.FakeMRs)) 35 | for i, v := range f.FakeMRs { 36 | mrs[i] = v 37 | } 38 | return mrs 39 | } 40 | 41 | // FakeMR encapsulates the value of a FakeMR at a point in time. 42 | type FakeMR struct { 43 | Index int 44 | Digest []byte 45 | DigestAlg crypto.Hash 46 | } 47 | 48 | // Idx gives the FakeMR index. 49 | func (f FakeMR) Idx() int { 50 | return f.Index 51 | } 52 | 53 | // Dgst gives the FakeMR digest. 54 | func (f FakeMR) Dgst() []byte { 55 | return f.Digest 56 | } 57 | 58 | // DgstAlg gives the FakeMR digest algorithm as a crypto.Hash. 59 | func (f FakeMR) DgstAlg() crypto.Hash { 60 | return f.DigestAlg 61 | } 62 | -------------------------------------------------------------------------------- /proto/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package proto contains protocol buffers that are exchanged between the client 16 | // and server. 17 | // 18 | // # Generating Protocol Buffer Code 19 | // 20 | // Anytime the Protocol Buffer definitions change, the generated Go code must be 21 | // regenerated. This can be done with "go generate". Just run: 22 | // 23 | // go generate ./... 24 | // 25 | // Upstream documentation: 26 | // https://developers.google.com/protocol-buffers/docs/reference/go-generated 27 | // 28 | // # Code Generation Dependencies 29 | // 30 | // To generate the Go code, your system must have "protoc" installed. See: 31 | // https://github.com/protocolbuffers/protobuf#protocol-compiler-installation 32 | // 33 | // The "protoc-gen-go" tool must also be installed. To install it, run: 34 | // 35 | // go install google.golang.org/protobuf/cmd/protoc-gen-go 36 | // 37 | // If you see a 'protoc-gen-go: program not found or is not executable' error 38 | // for the 'go generate' command, run the following: 39 | // 40 | // echo 'export PATH=$PATH:$GOPATH/bin' >> $HOME/.bashrc 41 | // source $HOME/.bashrc 42 | package proto 43 | 44 | //go:generate protoc --go_out=. --go_opt=module=github.com/google/go-eventlog/proto state.proto 45 | -------------------------------------------------------------------------------- /legacy/legacyevent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package legacy contains the legacy API adaptors for use by go-attestation. 16 | package legacy 17 | 18 | import "github.com/google/go-eventlog/tcg" 19 | 20 | // Event is a single event from a TCG event log. This reports descrete items such 21 | // as BIOS measurements or EFI states. 22 | // 23 | // There are many pitfalls for using event log events correctly to determine the 24 | // state of a machine[1]. In general it's much safer to only rely on the raw PCR 25 | // values and use the event log for debugging. 26 | // 27 | // [1] https://github.com/google/go-attestation/blob/master/docs/event-log-disclosure.md 28 | type Event struct { 29 | // Sequence gives the order of the event in the event log. 30 | Sequence int 31 | // Index of the PCR that this event was replayed against. 32 | Index int 33 | // Untrusted type of the event. This value is not verified by event log replays 34 | // and can be tampered with. It should NOT be used without additional context, 35 | // and unrecognized event types should result in errors. 36 | Type tcg.EventType 37 | 38 | // Data of the event. For certain kinds of events, this must match the event 39 | // digest to be valid. 40 | Data []byte 41 | // Digest is the verified digest of the event data. While an event can have 42 | // multiple for different hash values, this is the one that was matched to the 43 | // PCR value. 44 | Digest []byte 45 | 46 | // TODO(ericchiang): Provide examples or links for which event types must 47 | // match their data to their digest. 48 | } 49 | -------------------------------------------------------------------------------- /cel/fake_tlv.go: -------------------------------------------------------------------------------- 1 | package cel 2 | 3 | import ( 4 | "crypto" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | // FakeEventType indicates the CELR event is a Fake content type. 10 | FakeEventType uint8 = 222 11 | // FakeEventMR is the PCR which should be used for FakeEventType events. 12 | FakeEventMR = 23 13 | ) 14 | 15 | // FakeType represent a Fake content type in a CEL record content. 16 | type FakeType uint8 17 | 18 | // Type for Fake nested events 19 | const ( 20 | FakeEvent1 FakeType = iota 21 | FakeEvent2 22 | ) 23 | 24 | // FakeTlv is a specific TLV created for testing. 25 | type FakeTlv struct { 26 | EventType FakeType 27 | EventContent []byte 28 | } 29 | 30 | // TLV returns the TLV representation of the fake TLV. 31 | func (f FakeTlv) TLV() (TLV, error) { 32 | data, err := TLV{uint8(f.EventType), f.EventContent}.MarshalBinary() 33 | if err != nil { 34 | return TLV{}, err 35 | } 36 | 37 | return TLV{ 38 | Type: FakeEventType, 39 | Value: data, 40 | }, nil 41 | } 42 | 43 | // GenerateDigest generates the digest for the given fake TLV. The whole TLV struct will 44 | // be marshaled to bytes and feed into the hash algo. 45 | func (f FakeTlv) GenerateDigest(hashAlgo crypto.Hash) ([]byte, error) { 46 | contentTLV, err := f.TLV() 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | b, err := contentTLV.MarshalBinary() 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | hash := hashAlgo.New() 57 | if _, err = hash.Write(b); err != nil { 58 | return nil, err 59 | } 60 | return hash.Sum(nil), nil 61 | } 62 | 63 | // ParseToFakeTlv constructs a FakeTlv from a TLV. It will check for the correct fake event 64 | // type, and unmarshal the nested event. 65 | func (t TLV) ParseToFakeTlv() (FakeTlv, error) { 66 | if !t.IsFakeTLV() { 67 | return FakeTlv{}, fmt.Errorf("TLV type %v is not a Fake event", t.Type) 68 | } 69 | nestedEvent := TLV{} 70 | err := nestedEvent.UnmarshalBinary(t.Value) 71 | if err != nil { 72 | return FakeTlv{}, err 73 | } 74 | return FakeTlv{FakeType(nestedEvent.Type), nestedEvent.Value}, nil 75 | } 76 | 77 | // IsFakeTLV check whether a TLV is a Fake TLV by its Type value. 78 | func (t TLV) IsFakeTLV() bool { 79 | return t.Type == FakeEventType 80 | } 81 | -------------------------------------------------------------------------------- /extract/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "crypto" 19 | "testing" 20 | ) 21 | 22 | func TestNullTerminatedDataDigest(t *testing.T) { 23 | rawdata := []byte("123456") 24 | rawdataNullTerminated := []byte("123456\x00") 25 | rawdataModifyLastByte := []byte("123456\xff") 26 | hash := crypto.SHA256 27 | hasher := hash.New() 28 | hasher.Write(rawdata) 29 | rawDigest := hasher.Sum(nil) 30 | hasher.Reset() 31 | hasher.Write(rawdataNullTerminated) 32 | nullTerminatedDigest := hasher.Sum(nil) 33 | hasher.Reset() 34 | 35 | if err := verifyDataDigest(hasher, rawdata, rawDigest); err != nil { 36 | t.Error(err) 37 | } 38 | if err := verifyDataDigest(hasher, rawdata, nullTerminatedDigest); err == nil { 39 | t.Errorf("non null-terminated data should not match the null-terminated digest") 40 | } 41 | 42 | // "rawdata + '\x00'" can be verified with digest("rawdata") as well as digest("rawdata + '\x00'") 43 | if err := verifyNullTerminatedDataDigest(hasher, rawdataNullTerminated, nullTerminatedDigest); err != nil { 44 | t.Error(err) 45 | } 46 | if err := verifyNullTerminatedDataDigest(hasher, rawdataNullTerminated, rawDigest); err != nil { 47 | t.Error(err) 48 | } 49 | 50 | if err := verifyNullTerminatedDataDigest(hasher, rawdata, nullTerminatedDigest); err == nil { 51 | t.Errorf("non null-terminated data should always fail") 52 | } 53 | if err := verifyNullTerminatedDataDigest(hasher, rawdataModifyLastByte, nullTerminatedDigest); err == nil { 54 | t.Errorf("manipulated null terminated data should fail") 55 | } 56 | if err := verifyNullTerminatedDataDigest(hasher, []byte{}, []byte{}); err == nil { 57 | t.Errorf("len() == 0 should always fail") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tpmeventlog/replay.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package tpmeventlog implements event log parsing and replay for the PC Client 16 | // TPM PCR_based event log. 17 | // It supports both the SHA-1 only and crypto agile log formats. 18 | package tpmeventlog 19 | 20 | import ( 21 | "github.com/google/go-eventlog/extract" 22 | pb "github.com/google/go-eventlog/proto/state" 23 | "github.com/google/go-eventlog/register" 24 | "github.com/google/go-eventlog/tcg" 25 | ) 26 | 27 | // ReplayAndExtract parses a PC Client event log and replays the parsed 28 | // event log against the PCR bank specified by hash. 29 | // 30 | // It then extracts event info from the verified log into a FirmwareLogState. 31 | // It returns an error on failing to replay the events against the PCR bank or 32 | // on failing to parse malformed events. 33 | // 34 | // The returned FirmwareLogState may be a partial FirmwareLogState. 35 | // In the case of a partially filled state, err will be non-nil. 36 | // Callers can look for individual errors using `errors.Is`. 37 | // 38 | // It is the caller's responsibility to ensure that the passed PCR values can be 39 | // trusted. Users can establish trust in PCR values by either calling 40 | // client.ReadPCRs() themselves or by verifying the values via a PCR quote. 41 | func ReplayAndExtract(rawEventLog []byte, pcrBank register.PCRBank, opts extract.Opts) (*pb.FirmwareLogState, error) { 42 | cryptoHash, err := pcrBank.CryptoHash() 43 | if err != nil { 44 | return &pb.FirmwareLogState{}, err 45 | } 46 | events, err := tcg.ParseAndReplay(rawEventLog, pcrBank.MRs(), tcg.ParseOpts{}) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return extract.FirmwareLogState(events, cryptoHash, extract.TPMRegisterConfig, opts) 52 | } 53 | -------------------------------------------------------------------------------- /register/rtmr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package register 16 | 17 | import ( 18 | "crypto" 19 | ) 20 | 21 | /* 22 | RTMR0 => PCR1,7 23 | RTMR1 => PCR2-6 24 | RTMR2 => PCR8-15 25 | RTMR3 => N/A (for userspace) 26 | */ 27 | 28 | // RTMRBank is a bank of RTMRs that all correspond to the SHA-384 algorithm. 29 | type RTMRBank struct { 30 | RTMRs []RTMR 31 | } 32 | 33 | // CryptoHash returns the crypto.Hash algorithm related to the RTMR bank. 34 | func (b RTMRBank) CryptoHash() (crypto.Hash, error) { 35 | return crypto.SHA384, nil 36 | } 37 | 38 | // MRs returns a slice of MR from the RTMR implementation. 39 | func (b RTMRBank) MRs() []MR { 40 | mrs := make([]MR, len(b.RTMRs)) 41 | for i, v := range b.RTMRs { 42 | mrs[i] = v 43 | } 44 | return mrs 45 | } 46 | 47 | // RTMR encapsulates the value of a TDX runtime measurement register at a point 48 | // in time. The given RTMR must always have a SHA-384 digest. 49 | type RTMR struct { 50 | // The RTMR Index, not the CC MR Index. e.g., for RTMR[1], put 1, not 2. 51 | Index int 52 | Digest []byte 53 | } 54 | 55 | // Idx gives the CC Measurement Register index. 56 | // This value is the one used in Confidential Computing event logs. 57 | // Confusingly, MRTD uses CC Measurement Register Index 0, so RTMR0 uses 1. 58 | // RTMR1 uses 2, and so on. 59 | // https://cdrdv2-public.intel.com/726792/TDX%20Guest-Hypervisor%20Communication%20Interface_1.5_348552_004%20-%2020230317.pdf 60 | // https://github.com/cc-api/cc-trusted-vmsdk/issues/50 61 | func (r RTMR) Idx() int { 62 | return r.Index + 1 63 | } 64 | 65 | // Dgst gives the RTMR digest. 66 | func (r RTMR) Dgst() []byte { 67 | return r.Digest 68 | } 69 | 70 | // DgstAlg gives the RTMR digest algorithm as a crypto.Hash. 71 | func (r RTMR) DgstAlg() crypto.Hash { 72 | return crypto.SHA384 73 | } 74 | -------------------------------------------------------------------------------- /ccel/replay.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package ccel 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/google/go-eventlog/extract" 21 | pb "github.com/google/go-eventlog/proto/state" 22 | "github.com/google/go-eventlog/register" 23 | "github.com/google/go-eventlog/tcg" 24 | ) 25 | 26 | // ReplayAndExtract parses a Confidential Computing event log and 27 | // replays the parsed event log against the RTMR bank specified by hash. 28 | // 29 | // It then extracts event info from the verified log into a FirmwareLogState. 30 | // It returns an error on failing to replay the events against the RTMR bank or 31 | // on failing to parse malformed events. 32 | // 33 | // The returned FirmwareLogState may be a partial FirmwareLogState. 34 | // In the case of a partially filled state, err will be non-nil. 35 | // Callers can look for individual errors using `errors.Is`. 36 | // 37 | // It is the caller's responsibility to ensure that the passed RTMR values can be 38 | // trusted. Users can establish trust in RTMR values by either calling 39 | // client.ReadRTMRs() themselves or by verifying the values via a RTMR quote. 40 | func ReplayAndExtract(acpiTableFile []byte, rawEventLog []byte, rtmrBank register.RTMRBank, opts extract.Opts) (*pb.FirmwareLogState, error) { 41 | table, err := parseCCELACPITable(acpiTableFile) 42 | if err != nil { 43 | return nil, fmt.Errorf("failed to parse CCEL ACPI Table file: %v", err) 44 | } 45 | if table.CCType != TDX { 46 | return nil, fmt.Errorf("only TDX Confidential Computing event logs are supported: received %v", table.CCType) 47 | } 48 | 49 | cryptoHash, err := rtmrBank.CryptoHash() 50 | if err != nil { 51 | return &pb.FirmwareLogState{}, err 52 | } 53 | // CCELs have trailing padding at the end of the event log. 54 | events, err := tcg.ParseAndReplay(rawEventLog, rtmrBank.MRs(), tcg.ParseOpts{AllowPadding: true}) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return extract.FirmwareLogState(events, cryptoHash, extract.RTMRRegisterConfig, opts) 59 | } 60 | -------------------------------------------------------------------------------- /extract/rtmr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "bytes" 19 | "crypto" 20 | "errors" 21 | "fmt" 22 | 23 | pb "github.com/google/go-eventlog/proto/state" 24 | "github.com/google/go-eventlog/tcg" 25 | ) 26 | 27 | // GrubStateFromRTMRLog extracts GRUB commands from RTMR2. 28 | func GrubStateFromRTMRLog(hash crypto.Hash, events []tcg.Event) (*pb.GrubState, error) { 29 | var commands []string 30 | for eventNum, event := range events { 31 | ccMRIndex := event.MRIndex() 32 | if ccMRIndex != 3 { 33 | continue 34 | } 35 | 36 | // Skip parsing EV_EVENT_TAG event since it likely comes from Linux. 37 | if event.UntrustedType() == tcg.EventTag { 38 | continue 39 | } 40 | 41 | if event.UntrustedType() != tcg.Ipl { 42 | return nil, fmt.Errorf("invalid event type %v for PCR%d, expected EV_IPL", event.UntrustedType().String(), ccMRIndex) 43 | } 44 | 45 | hasher := hash.New() 46 | suffixAt := -1 47 | rawData := event.RawData() 48 | for _, prefix := range validPrefixes { 49 | if bytes.HasPrefix(rawData, prefix) { 50 | suffixAt = len(prefix) 51 | break 52 | } 53 | } 54 | if suffixAt == -1 { 55 | continue 56 | } 57 | 58 | // Check the slice is not empty after the suffix, which ensures rawData[len(rawData)-1] is not part 59 | // of the suffix. 60 | if len(rawData[suffixAt:]) > 0 && rawData[len(rawData)-1] == '\x00' { 61 | if err := verifyNullTerminatedDataDigest(hasher, rawData[suffixAt:], event.ReplayedDigest()); err != nil { 62 | return nil, fmt.Errorf("invalid GRUB event (null-terminated) #%d: %v", eventNum, err) 63 | } 64 | } else { 65 | if err := verifyDataDigest(hasher, rawData[suffixAt:], event.ReplayedDigest()); err != nil { 66 | return nil, fmt.Errorf("invalid GRUB event #%d: %v", eventNum, err) 67 | } 68 | } 69 | hasher.Reset() 70 | commands = append(commands, string(rawData)) 71 | } 72 | if len(commands) == 0 { 73 | return nil, errors.New("no GRUB measurements found") 74 | } 75 | return &pb.GrubState{Commands: commands}, nil 76 | } 77 | -------------------------------------------------------------------------------- /tcg/eventlog_workarounds.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package tcg 16 | 17 | type elWorkaround struct { 18 | id string 19 | affectedPCR int 20 | apply func(e *EventLog) error 21 | } 22 | 23 | // inject3 appends two new events into the event log. 24 | func inject3(e *EventLog, pcr int, data1, data2, data3 string) error { 25 | if err := inject(e, pcr, data1); err != nil { 26 | return err 27 | } 28 | if err := inject(e, pcr, data2); err != nil { 29 | return err 30 | } 31 | return inject(e, pcr, data3) 32 | } 33 | 34 | // inject2 appends two new events into the event log. 35 | func inject2(e *EventLog, pcr int, data1, data2 string) error { 36 | if err := inject(e, pcr, data1); err != nil { 37 | return err 38 | } 39 | return inject(e, pcr, data2) 40 | } 41 | 42 | // inject appends a new event into the event log. 43 | func inject(e *EventLog, pcr int, data string) error { 44 | evt := rawEvent{ 45 | data: []byte(data), 46 | index: pcr, 47 | sequence: e.rawEvents[len(e.rawEvents)-1].sequence + 1, 48 | } 49 | for _, alg := range e.Algs { 50 | h := alg.CryptoHash().New() 51 | h.Write([]byte(data)) 52 | evt.digests = append(evt.digests, digest{hash: alg.CryptoHash(), data: h.Sum(nil)}) 53 | } 54 | e.rawEvents = append(e.rawEvents, evt) 55 | return nil 56 | } 57 | 58 | const ( 59 | ebsInvocation = "Exit Boot Services Invocation" 60 | ebsSuccess = "Exit Boot Services Returned with Success" 61 | ebsFailure = "Exit Boot Services Returned with Failure" 62 | ) 63 | 64 | // EventlogWorkarounds fix known event log issues/bugs. 65 | var EventlogWorkarounds = []elWorkaround{ 66 | { 67 | id: "EBS Invocation + Success", 68 | affectedPCR: 5, 69 | apply: func(e *EventLog) error { 70 | return inject2(e, 5, ebsInvocation, ebsSuccess) 71 | }, 72 | }, 73 | { 74 | id: "EBS Invocation + Failure", 75 | affectedPCR: 5, 76 | apply: func(e *EventLog) error { 77 | return inject2(e, 5, ebsInvocation, ebsFailure) 78 | }, 79 | }, 80 | { 81 | id: "EBS Invocation + Failure + Success", 82 | affectedPCR: 5, 83 | apply: func(e *EventLog) error { 84 | return inject3(e, 5, ebsInvocation, ebsFailure, ebsSuccess) 85 | }, 86 | }, 87 | } 88 | -------------------------------------------------------------------------------- /extract/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "bytes" 19 | "crypto" 20 | "crypto/sha1" 21 | "crypto/sha256" 22 | "encoding/hex" 23 | "errors" 24 | "fmt" 25 | "hash" 26 | 27 | "github.com/google/go-eventlog/tcg" 28 | ) 29 | 30 | // DigestEquals returns an error if the Event digest does not match the slice. 31 | func DigestEquals(e tcg.Event, b []byte) error { 32 | digest := e.ReplayedDigest() 33 | 34 | if len(digest) == 0 { 35 | return errors.New("no digests present") 36 | } 37 | switch len(digest) { 38 | case crypto.SHA384.Size(): 39 | hasher := crypto.SHA384.New() 40 | hasher.Write(b) 41 | if bytes.Equal(hasher.Sum(nil), digest) { 42 | return nil 43 | } 44 | case crypto.SHA256.Size(): 45 | s := sha256.Sum256(b) 46 | if bytes.Equal(s[:], digest) { 47 | return nil 48 | } 49 | case crypto.SHA1.Size(): 50 | s := sha1.Sum(b) 51 | if bytes.Equal(s[:], digest) { 52 | return nil 53 | } 54 | default: 55 | return fmt.Errorf("cannot compare hash of length %d", len(digest)) 56 | } 57 | 58 | return fmt.Errorf("digest (len %d) does not match", len(digest)) 59 | } 60 | 61 | // verifyNullTerminatedRawData checks the digest of the data. 62 | // Returns nil if digest match the hash of the data or the data without the last bytes (\x00). 63 | // The caller needs to make sure len(data) is at least 1, and data is ended with '\x00', 64 | // otherwise this function will return an error. 65 | func verifyNullTerminatedDataDigest(hasher hash.Hash, data []byte, digest []byte) error { 66 | if len(data) == 0 || data[len(data)-1] != '\x00' { 67 | return errors.New("given data is not null-terminated") 68 | } 69 | if err := verifyDataDigest(hasher, data, digest); err != nil { 70 | if err := verifyDataDigest(hasher, data[:len(data)-1], digest); err != nil { 71 | return err 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | // verifyDataDigest checks the digest of the data. 78 | func verifyDataDigest(hasher hash.Hash, data []byte, digest []byte) error { 79 | hasher.Reset() 80 | hasher.Write(data) 81 | defer hasher.Reset() 82 | if !bytes.Equal(digest, hasher.Sum(nil)) { 83 | return fmt.Errorf("invalid digest: %s", hex.EncodeToString(digest)) 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /extract/pcr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "bytes" 19 | "crypto" 20 | "errors" 21 | "fmt" 22 | 23 | pb "github.com/google/go-eventlog/proto/state" 24 | "github.com/google/go-eventlog/tcg" 25 | ) 26 | 27 | // GrubStateFromTPMLog extracts GRUB commands from PCR8 and GRUB files from 9. 28 | func GrubStateFromTPMLog(hash crypto.Hash, events []tcg.Event) (*pb.GrubState, error) { 29 | var files []*pb.GrubFile 30 | var commands []string 31 | for eventNum, event := range events { 32 | index := event.MRIndex() 33 | if index != 8 && index != 9 { 34 | continue 35 | } 36 | 37 | // Skip parsing EV_EVENT_TAG event since it likely comes from Linux. 38 | if event.UntrustedType() == tcg.EventTag { 39 | continue 40 | } 41 | 42 | if event.UntrustedType() != tcg.Ipl { 43 | return nil, fmt.Errorf("invalid event type for PCR%d, expected EV_IPL", index) 44 | } 45 | 46 | if index == 9 { 47 | files = append(files, &pb.GrubFile{Digest: event.ReplayedDigest(), 48 | UntrustedFilename: event.RawData()}) 49 | } else if index == 8 { 50 | hasher := hash.New() 51 | suffixAt := -1 52 | rawData := event.RawData() 53 | for _, prefix := range validPrefixes { 54 | if bytes.HasPrefix(rawData, prefix) { 55 | suffixAt = len(prefix) 56 | break 57 | } 58 | } 59 | if suffixAt == -1 { 60 | return nil, fmt.Errorf("invalid prefix seen for PCR%d event: %s", index, rawData) 61 | } 62 | 63 | // Check the slice is not empty after the suffix, which ensures rawData[len(rawData)-1] is not part 64 | // of the suffix. 65 | if len(rawData[suffixAt:]) > 0 && rawData[len(rawData)-1] == '\x00' { 66 | if err := verifyNullTerminatedDataDigest(hasher, rawData[suffixAt:], event.ReplayedDigest()); err != nil { 67 | return nil, fmt.Errorf("invalid GRUB event (null-terminated) #%d: %v", eventNum, err) 68 | } 69 | } else { 70 | if err := verifyDataDigest(hasher, rawData[suffixAt:], event.ReplayedDigest()); err != nil { 71 | return nil, fmt.Errorf("invalid GRUB event #%d: %v", eventNum, err) 72 | } 73 | } 74 | hasher.Reset() 75 | commands = append(commands, string(rawData)) 76 | } 77 | } 78 | if len(files) == 0 && len(commands) == 0 { 79 | return nil, errors.New("no GRUB measurements found") 80 | } 81 | return &pb.GrubState{Files: files, Commands: commands}, nil 82 | } 83 | -------------------------------------------------------------------------------- /ccel/cceventlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package ccel implements event log parsing and replay for the Confidential Computing event log. 16 | // It only supports the CCEL based on the TCG crypto-agile event log (including 17 | // the "Spec ID Event03" signature). 18 | package ccel 19 | 20 | import ( 21 | "encoding/binary" 22 | "fmt" 23 | ) 24 | 25 | /* 26 | MrIndex = 0; 27 | if (PCRIndex == 0) { 28 | MrIndex = CC_MR_INDEX_0_MRTD; 29 | } else if ((PCRIndex == 1) || (PCRIndex == 7)) { 30 | MrIndex = CC_MR_INDEX_1_RTMR0; 31 | } else if ((PCRIndex >= 2) && (PCRIndex <= 6)) { 32 | MrIndex = CC_MR_INDEX_2_RTMR1; 33 | } else if ((PCRIndex >= 8) && (PCRIndex <= 15)) { 34 | MrIndex = CC_MR_INDEX_3_RTMR2; 35 | } 36 | */ 37 | 38 | // Defined in Guest Hypervisor Communication Interface (GHCI) for Intel TDX 1.0. 39 | // https://www.intel.com/content/www/us/en/content-details/726790/guest-host-communication-interface-ghci-for-intel-trust-domain-extensions-intel-tdx.html 40 | const ( 41 | // See Section 4.3.3 CC-Event Log 42 | CCELACPITableSig = "CCEL" 43 | CCELACPITableMinSize = 56 44 | ) 45 | 46 | // CCType describes the Confidential Computing type for the Confidential 47 | // Computing event log. 48 | type CCType uint8 49 | 50 | // Known CC types. 51 | // See https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#cc-event-log-acpi-table. 52 | const ( 53 | Reserved = iota 54 | SEV 55 | TDX 56 | ) 57 | 58 | // CCACPITable represents the confidential computing (CC) event log ACPI table. 59 | type CCACPITable struct { 60 | Length uint32 61 | CCType 62 | } 63 | 64 | func parseCCELACPITable(acpiTableFile []byte) (CCACPITable, error) { 65 | if len(acpiTableFile) < CCELACPITableMinSize { 66 | return CCACPITable{}, fmt.Errorf("received a smaller CCEL ACPI Table size (%v) than expected (%v)", len(acpiTableFile), CCELACPITableMinSize) 67 | } 68 | sig := acpiTableFile[0:4] 69 | if CCELACPITableSig != string(sig) { 70 | return CCACPITable{}, fmt.Errorf("received an invalid signature (%v) for CCEL ACPI Table size (%v)", string(sig), len(acpiTableFile)) 71 | 72 | } 73 | 74 | tableLenBytes := acpiTableFile[4:8] 75 | tableLen := binary.LittleEndian.Uint32(tableLenBytes) 76 | if tableLen != uint32(len(acpiTableFile)) { 77 | return CCACPITable{}, fmt.Errorf("received mismatch CCEL ACPI table length: got %v, expected %v", tableLen, uint32(len(acpiTableFile))) 78 | } 79 | 80 | ccType := acpiTableFile[36] 81 | if ccType > 2 { 82 | return CCACPITable{}, fmt.Errorf("received unknown CC type: %d", ccType) 83 | } 84 | 85 | logAreaMinLenBytes := acpiTableFile[40:48] 86 | laml := binary.LittleEndian.Uint32(logAreaMinLenBytes) 87 | return CCACPITable{ 88 | Length: laml, 89 | CCType: CCType(ccType), 90 | }, nil 91 | } 92 | -------------------------------------------------------------------------------- /register/fake_rot.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package register 16 | 17 | import ( 18 | "crypto" 19 | "fmt" 20 | ) 21 | 22 | // FakeROT implements a fake root-of-trust for measurement for test. 23 | type FakeROT struct { 24 | fakeMRBanks map[crypto.Hash]map[int][]byte 25 | } 26 | 27 | // CreateFakeRot creates a fake root-of-trust with banks corresponding to the 28 | // given hash algorithms, each of size numIdxs. 29 | func CreateFakeRot(hashes []crypto.Hash, numIdxs int) (FakeROT, error) { 30 | if len(hashes) == 0 || numIdxs <= 0 { 31 | return FakeROT{}, fmt.Errorf("hashes (%v) or numIdxs (%v) was empty", hashes, numIdxs) 32 | } 33 | fakeMRBanks := make(map[crypto.Hash]map[int][]byte) 34 | for _, hash := range hashes { 35 | fakeBank := make(map[int][]byte) 36 | for idx := 0; idx < numIdxs; idx++ { 37 | zeroesMR := make([]byte, hash.Size()) 38 | fakeBank[idx] = zeroesMR 39 | } 40 | fakeMRBanks[hash] = fakeBank 41 | } 42 | return FakeROT{fakeMRBanks: fakeMRBanks}, nil 43 | } 44 | 45 | // Digest returns the current digest for the given measurement register indicated by FakeMR. 46 | func (f FakeROT) Digest(mr FakeMR) ([]byte, error) { 47 | hash := mr.DigestAlg 48 | idx := mr.Index 49 | bank, ok := f.fakeMRBanks[hash] 50 | if !ok { 51 | return nil, fmt.Errorf("bank %v not present in fake root of trust", hash) 52 | } 53 | 54 | dgst, ok := bank[idx] 55 | if !ok { 56 | return nil, fmt.Errorf("MR index %v in bank %v not present in fake root of trust", idx, hash) 57 | } 58 | if len(dgst) != hash.Size() { 59 | return nil, fmt.Errorf("MR index %v in bank %v contained invalid size %v, expected %v", idx, hash, len(dgst), hash.Size()) 60 | } 61 | return dgst, nil 62 | } 63 | 64 | // ReadMRs returns the MRs given by the hash algo and MR index selection. 65 | func (f FakeROT) ReadMRs(hash crypto.Hash, mrSelection []int) (FakeMRBank, error) { 66 | bank, ok := f.fakeMRBanks[hash] 67 | if !ok { 68 | return FakeMRBank{}, fmt.Errorf("bank %v not present in fake root of trust", hash) 69 | } 70 | fakeMRs := make([]FakeMR, 0, len(bank)) 71 | for _, mrIdx := range mrSelection { 72 | dgst, ok := bank[mrIdx] 73 | if !ok { 74 | return FakeMRBank{}, fmt.Errorf("index %v not present in bank %v", mrIdx, hash) 75 | } 76 | fakeMRs = append(fakeMRs, FakeMR{ 77 | Index: mrIdx, 78 | Digest: dgst, 79 | DigestAlg: hash, 80 | }) 81 | } 82 | return FakeMRBank{Hash: hash, FakeMRs: fakeMRs}, nil 83 | } 84 | 85 | // ExtendMR extends the FakeROT's internal MRs corresponding to the bank, index 86 | // with the digest specified in mr. 87 | func (f FakeROT) ExtendMR(mr FakeMR) error { 88 | hash := mr.DigestAlg 89 | digest := mr.Digest 90 | idx := mr.Index 91 | if len(digest) != mr.DigestAlg.Size() { 92 | return fmt.Errorf("invalid digest size %v for algo %v, expected %v", len(digest), hash, hash.Size()) 93 | } 94 | 95 | mrDigest, err := f.Digest(mr) 96 | if err != nil { 97 | return fmt.Errorf("failed to extend index %v in bank %v: %v", idx, hash, err) 98 | } 99 | 100 | hasher := hash.New() 101 | hasher.Write(mrDigest) 102 | hasher.Write(digest) 103 | 104 | f.fakeMRBanks[hash][idx] = hasher.Sum(nil) 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /extract/registerconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "crypto" 19 | 20 | pb "github.com/google/go-eventlog/proto/state" 21 | "github.com/google/go-eventlog/tcg" 22 | ) 23 | 24 | // registerConfig contains the measurement register technology-specific indexes 25 | // expected to contain the events corresponding to various states, like EFI 26 | // and Secure Boot states. 27 | // This uses the event log-encoded index, e.g., PCR or CC MR (not RTMR). 28 | type registerConfig struct { 29 | Name string 30 | FirmwareDriverIdx uint32 31 | SecureBootIdx uint32 32 | EFIAppIdx uint32 33 | ExitBootServicesIdx uint32 34 | GRUBCmdIdx uint32 35 | GRUBFileIdx uint32 36 | GRUBExtracter func(crypto.Hash, []tcg.Event) (*pb.GrubState, error) 37 | PlatformExtracter func(crypto.Hash, []tcg.Event) (*pb.PlatformState, error) 38 | AdditionalSecureBootIdxEvents map[tcg.EventType]bool 39 | LogType pb.LogType 40 | } 41 | 42 | // TPMRegisterConfig configures the expected indexes and event types for 43 | // TPM-based event logs. 44 | var TPMRegisterConfig = registerConfig{ 45 | Name: "PCR", 46 | FirmwareDriverIdx: 2, 47 | SecureBootIdx: 7, 48 | EFIAppIdx: 4, 49 | ExitBootServicesIdx: 5, 50 | GRUBCmdIdx: 8, 51 | GRUBFileIdx: 9, 52 | GRUBExtracter: GrubStateFromTPMLog, 53 | PlatformExtracter: PlatformState, 54 | // AdditionalSecureBootIdxEvents is empty since 55 | // eventparse.ParseSecurebootState encodes all the current allowable types 56 | // for PCR 7. 57 | LogType: pb.LogType_LOG_TYPE_TCG2, 58 | } 59 | 60 | // RTMRRegisterConfig configures the expected indexes and event types for 61 | // RTMR-based event logs. 62 | var RTMRRegisterConfig = registerConfig{ 63 | Name: "RTMR", 64 | // CCMR2=RTMR[1]=PCR[2] 65 | FirmwareDriverIdx: 2, 66 | // CCMR1=RTMR[0]=PCR[7] 67 | SecureBootIdx: 1, 68 | // CCMR2=RTMR[1]=PCR[4] 69 | EFIAppIdx: 2, 70 | /// CCMR2=RTMR[1]=PCR[5] 71 | ExitBootServicesIdx: 2, 72 | // CCMR3=RTMR[2]=PCR[8] 73 | GRUBCmdIdx: 3, 74 | // CCMR3=RTMR[2]=PCR[9] 75 | GRUBFileIdx: 3, 76 | GRUBExtracter: GrubStateFromRTMRLog, 77 | PlatformExtracter: func(_ crypto.Hash, _ []tcg.Event) (*pb.PlatformState, error) { 78 | return &pb.PlatformState{Technology: pb.GCEConfidentialTechnology_INTEL_TDX}, nil 79 | }, 80 | // RTMR[0] maps to both PCR[1] and PCR[7]. 81 | // Pulled from "Table 27 Events" in 82 | // "TCG PC Client Platform Firmware Profile Specification" 83 | AdditionalSecureBootIdxEvents: map[tcg.EventType]bool{ 84 | tcg.CPUMicrocode: true, 85 | tcg.PlatformConfigFlags: true, 86 | tcg.TableOfDevices: true, 87 | tcg.NonhostConfig: true, 88 | tcg.EFIVariableDriverConfig: true, 89 | tcg.EFIVariableBoot: true, 90 | tcg.EFIAction: true, 91 | tcg.EFIHandoffTables2: true, 92 | tcg.EFIVariableBoot2: true, 93 | // https://github.com/tianocore/edk2/blob/a29a9cce5f9afa32560d966e501247246ec96ef6/OvmfPkg/IntelTdx/TdxHelperLib/TdxMeasurementHob.c#L245 94 | // The following is not spec-compliant for PCR 1 or 7. The spec says [0, 2, 4]. 95 | tcg.EFIPlatformFirmwareBlob2: true, 96 | }, 97 | LogType: pb.LogType_LOG_TYPE_CC, 98 | } 99 | -------------------------------------------------------------------------------- /ccel/replay_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package ccel 16 | 17 | import ( 18 | "os" 19 | "strconv" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/google/go-eventlog/extract" 24 | "github.com/google/go-eventlog/register" 25 | ) 26 | 27 | func TestReplayAndExtract(t *testing.T) { 28 | tableBytes, err := os.ReadFile("../testdata/eventlogs/ccel/cos-113-intel-tdx.table.bin") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | tests := []struct { 33 | el eventLog 34 | opts extract.Opts 35 | wantErr bool 36 | }{ 37 | { 38 | el: COS113TDX, 39 | opts: extract.Opts{Loader: extract.GRUB}, 40 | }, 41 | { 42 | el: GDCCCEL, 43 | opts: extract.Opts{Loader: extract.GRUB, AllowEmptySBVar: true}, 44 | }, 45 | { 46 | el: GDCCCEL, 47 | opts: extract.Opts{Loader: extract.GRUB}, 48 | wantErr: true, 49 | }, 50 | } 51 | for _, tt := range tests { 52 | t.Run(tt.el.fname+strconv.FormatBool(tt.wantErr), func(t *testing.T) { 53 | elBytes, err := os.ReadFile(tt.el.fname) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | _, err = ReplayAndExtract(tableBytes, elBytes, register.RTMRBank{RTMRs: tt.el.rtmrs}, tt.opts) 59 | if (err != nil) != tt.wantErr { 60 | t.Errorf("ReplayAndExtract: got %v, wantErr %v", err, tt.wantErr) 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestReplayAndExtractFailDuplicateSeparator(t *testing.T) { 67 | badELWithUEFIBug, err := os.ReadFile("../testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator.bin") 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | tableBytes, err := os.ReadFile("../testdata/eventlogs/ccel/CCEL.bin") 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | rtmr0 := []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-") 77 | rtmr1 := []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 ") 78 | 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") 79 | bank := register.RTMRBank{RTMRs: []register.RTMR{ 80 | // {Index: 0, Digest: zeroes}, 81 | {Index: 0, Digest: rtmr0}, 82 | {Index: 1, Digest: rtmr1}, 83 | {Index: 2, Digest: rtmr2}, 84 | }} 85 | _, err = ReplayAndExtract(tableBytes, badELWithUEFIBug, bank, extract.Opts{Loader: extract.GRUB}) 86 | if err == nil || !strings.Contains(err.Error(), "duplicate separator at event") { 87 | t.Errorf("ReplayAndExtract(badELWithUEFIBug): got %v, expected error with duplicate separator message", err) 88 | } 89 | } 90 | 91 | func TestReplayAndExtractEmptyLog(t *testing.T) { 92 | 93 | tableBytes, err := os.ReadFile("../testdata/eventlogs/ccel/cos-113-intel-tdx.table.bin") 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | 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") 99 | 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") 100 | 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") 101 | bank := register.RTMRBank{RTMRs: []register.RTMR{ 102 | {Index: 0, Digest: rtmr0}, 103 | {Index: 1, Digest: rtmr1}, 104 | {Index: 2, Digest: rtmr2}, 105 | }} 106 | _, err = ReplayAndExtract(tableBytes, nil, bank, extract.Opts{}) 107 | if err != nil { 108 | t.Errorf("failed to ReplayAndExtract from CCEL: %v", err) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /testdata/eventlog_data.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package testdata contains event logs and event log-related info for testing. 16 | package testdata 17 | 18 | import _ "embed" // Necessary to use go:embed 19 | 20 | // Raw binary TCG Event Logs 21 | var ( 22 | //go:embed eventlogs/tpm/arch-linux-workstation.bin 23 | ArchLinuxWorkstationEventLog []byte 24 | //go:embed eventlogs/tpm/debian-10.bin 25 | Debian10EventLog []byte 26 | //go:embed eventlogs/tpm/glinux-alex.bin 27 | GlinuxAlexEventLog []byte 28 | //go:embed eventlogs/tpm/rhel8-uefi.bin 29 | Rhel8EventLog []byte 30 | //go:embed eventlogs/tpm/ubuntu-1804-amd-sev.bin 31 | Ubuntu1804AmdSevEventLog []byte 32 | //go:embed eventlogs/tpm/ubuntu-2104-no-dbx.bin 33 | Ubuntu2104NoDbxEventLog []byte 34 | //go:embed eventlogs/tpm/ubuntu-2104-no-secure-boot.bin 35 | Ubuntu2104NoSecureBootEventLog []byte 36 | //go:embed eventlogs/tpm/ubuntu-2404-amd-sevsnp.bin 37 | Ubuntu2404AmdSevSnpEventLog []byte 38 | //go:embed eventlogs/tpm/cos-85-amd-sev.bin 39 | Cos85AmdSevEventLog []byte 40 | //go:embed eventlogs/tpm/cos-93-amd-sev.bin 41 | Cos93AmdSevEventLog []byte 42 | //go:embed eventlogs/tpm/cos-101-amd-sev.bin 43 | Cos101AmdSevEventLog []byte 44 | //go:embed eventlogs/tpm/cos-121-amd-sev.bin 45 | Cos121AmdSevEventLog []byte 46 | //go:embed eventlogs/tpm/gdc-host.bin 47 | GdcHost []byte 48 | ) 49 | 50 | // Kernel command lines from event logs. 51 | var ( 52 | Cos85AmdSevCmdline = "/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 \"dm=1 vroot none ro 1,0 4077568 verity payload=PARTUUID=EF8ECEE2-2385-AE4F-A146-1ED93D8AC217 hashtree=PARTUUID=EF8ECEE2-2385-AE4F-A146-1ED93D8AC217 hashstart=4077568 alg=sha256 root_hexdigest=795872ee03859c10dfcc4d67b4b96c85094b340c2d8784783abc2fa12a6ed671 salt=40eb77fb9093cbff56a6f9c2214c4f7554817d079513b7c77de4953d6b8ffc16\"\x00" 53 | Cos93AmdSevCmdline = "/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 console=tty1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 \"dm=1 vroot none ro 1,0 4077568 verity payload=PARTUUID=05CDEDEA-42C6-2248-B6B3-AB4CE3EA7501 hashtree=PARTUUID=05CDEDEA-42C6-2248-B6B3-AB4CE3EA7501 hashstart=4077568 alg=sha256 root_hexdigest=8db95edb446a7311634fc8409e6eab39c66886c4db16aeeef166bbd8fe4ff357 salt=3ec6b6fef69119253b9a5f79a5bb06bc7b12f177063b2466a04f08976375af44\"\x00" 54 | Cos101AmdSevCmdline = "/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume loglevel=7 console=tty1 console=ttyS0 security=apparmor virtio_net.napi_tx=1 nmi_watchdog=0 csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 \"dm=1 vroot none ro 1,0 4077568 verity payload=PARTUUID=1D70214B-9AB3-E542-8372-3CCD786534FA hashtree=PARTUUID=1D70214B-9AB3-E542-8372-3CCD786534FA hashstart=4077568 alg=sha256 root_hexdigest=48d436350a7e83bde985cd3f7e79fa443557743b42243803ce31104ca4719c5d salt=b323b014b6f463172fca758a1c5a6745a2c8e5872be0e175e2f4b40c8295b2ab\"\x00" 55 | Ubuntu2404AmdSevSnpCmdline = "/vmlinuz-6.8.0-1010-gcp root=PARTUUID=8270f3c9-b4e4-4345-80ee-5a62db7ebf3f ro console=ttyS0,115200 panic=-1\x00" 56 | ) 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | # use this file except in compliance with the License. You may obtain a copy of 5 | # the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations under 13 | # the License. 14 | 15 | name: CI 16 | on: 17 | push: 18 | tags: 19 | - v* 20 | branches: 21 | - main 22 | pull_request: 23 | 24 | jobs: 25 | build: 26 | strategy: 27 | matrix: 28 | go-version: [1.20.x] 29 | # TODO: Get this working on windows-latest 30 | os: [ubuntu-latest] 31 | architecture: [x32, x64] 32 | include: 33 | - os: macos-latest 34 | architecture: arm64 35 | go-version: 1.20.x 36 | - os: macos-14-large 37 | architecture: x64 38 | go-version: 1.20.x 39 | name: Generate/Build/Test (${{ matrix.os }}, ${{ matrix.architecture }}, Go ${{ matrix.go-version }}) 40 | runs-on: ${{ matrix.os }} 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: actions/setup-go@v4 44 | with: 45 | go-version: ${{ matrix.go-version }} 46 | architecture: ${{ matrix.architecture }} 47 | - name: Install Protoc 48 | uses: arduino/setup-protoc@v1 49 | with: 50 | repo-token: ${{ secrets.GITHUB_TOKEN }} 51 | version: "3.20.1" 52 | - name: Install protoc-gen-go 53 | run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2 54 | - name: Check Protobuf Generation 55 | run: | 56 | go generate ./... 57 | git diff -G'^[^/]' --exit-code 58 | - name: Install Linux 64-bit packages 59 | run: sudo apt-get -y install libssl-dev 60 | if: runner.os == 'Linux' && matrix.architecture == 'x64' 61 | - name: Install Linux 32-bit packages 62 | run: sudo dpkg --add-architecture i386; sudo apt-get update; sudo apt-get -y install libssl-dev:i386 libgcc-s1:i386 gcc-multilib 63 | if: runner.os == 'Linux' && matrix.architecture == 'x32' 64 | - name: Install Mac packages 65 | run: | 66 | brew install openssl 67 | if: runner.os == 'macOS' 68 | - name: Install Windows packages 69 | run: choco install openssl 70 | if: runner.os == 'Windows' 71 | - name: Build all modules 72 | run: go build -v ./... 73 | - name: Test all modules 74 | run: go test -v ./... 75 | 76 | lint: 77 | strategy: 78 | matrix: 79 | go-version: [1.21.x] 80 | os: [ubuntu-latest] 81 | dir: ["./"] 82 | name: Lint ${{ matrix.dir }} (${{ matrix.os }}, Go ${{ matrix.go-version }}) 83 | runs-on: ${{ matrix.os }} 84 | steps: 85 | - uses: actions/checkout@v3 86 | - uses: actions/setup-go@v2 87 | with: 88 | go-version: ${{ matrix.go-version }} 89 | - name: Run golangci-lint 90 | uses: golangci/golangci-lint-action@v3.2.0 91 | with: 92 | version: latest 93 | working-directory: ${{ matrix.dir }} 94 | args: > 95 | -D errcheck 96 | -E stylecheck 97 | -E goimports 98 | -E misspell 99 | -E revive 100 | -E gofmt 101 | -E goimports 102 | --exclude-use-default=false 103 | --max-same-issues=0 104 | --max-issues-per-linter=0 105 | --timeout 2m 106 | 107 | lintc: 108 | strategy: 109 | matrix: 110 | go-version: [1.21.x] 111 | os: [ubuntu-latest] 112 | name: Lint CGO (${{ matrix.os }}, Go ${{ matrix.go-version }}) 113 | runs-on: ${{ matrix.os }} 114 | steps: 115 | - uses: actions/checkout@v3 116 | - uses: actions/setup-go@v2 117 | with: 118 | go-version: ${{ matrix.go-version }} 119 | - name: Install Linux packages 120 | run: sudo apt-get -y install libssl-dev 121 | - name: Check for CGO Warnings (gcc) 122 | run: CGO_CFLAGS=-Werror CC=gcc go build ./... 123 | - name: Check for CGO Warnings (clang) 124 | run: CGO_CFLAGS=-Werror CC=clang go build ./... 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Event Log 2 | 3 | Go Event Log is a library for handling various event logs for use in Measured Boot protocols. 4 | 5 | It is a companion for technologies that provide measurement registers and an event log, such as TPM PCRs and the TCG PC Client event log. 6 | 7 | Packages: 8 | - `ccel` 9 | - `cel` 10 | - `legacy` 11 | - `tpmeventlog` 12 | - `proto` 13 | - `register` 14 | - `wellknown` 15 | 16 | # Terminology 17 | Event log parsing is the process of resolving event log events against the registers in the Root of Trust for Measurement and extracting useful information from the verified events. At a high level, we can break it down into Quote Verification, Event Log Replay, and Event Parsing. 18 | 19 | ## Measured Boot 20 | To ensure the integrity of the event log, measurement registers contain the final digest of a chain of measurements. These registers are typically located on tamper-proof storage on a root-of-trust, like a TPM. With measurement registers, a verifier can detect changes to an event log through a mechanism called event log replay. On TPMs, measurement registers are called platform configuration registers, or PCRs. 21 | 22 | Typically, these measurement registers are not directly writable. They usually expose an Extend command that takes the existing value in the MR, concatenates it with the new event hash, and then hashes the concatenated value. 23 | 24 | MRnew = hash(MRold || hash(measured data)) 25 | 26 | Since hash functions are one way, replicating the same MR values without the same measurements is difficult. This is a good property: only the same boot configuration should yield the same MR values. However, in the same vein, MRs are difficult to use to craft a machine policy since any small change in the input or in measurement order yields wildly different MR values. 27 | 28 | ## Quote Verification 29 | A measured boot root of trust can issue a report of the measurement registers, often called a quote or attestation report. This quote is typically a digitally-signed digest of measurement registers. 30 | 31 | A verifier then checks that the digest of measurement registers are signed by a trustworthy key, the Root of Trust for Reporting (RTR). This RTR, aka attestation key, is typically a certified key that signs a report of the measurement registers. This certification is known as an Endorsement in the [IETF RATS Architecture](https://datatracker.ietf.org/doc/rfc9334/). 32 | 33 | NOTE: This library does not support quote verification. Integrating code is expected to first verify a quote before using the facilities for event log replay and parsing. 34 | 35 | ## Event Log Replay 36 | Event log replay involves deserializing a raw event log and using the events to recalculate all of the measurement registers. Each event contains a digest and a measurement register index. The verifier will create simulated measurement registers and, for each event, extend the event digest into its corresponding simulated register. At the end, the verifier compares the simulated register values against the actual quoted measurement register values from the first step. 37 | 38 | Technology-specific logic in this repo includes deserializing a raw event log binary. For example, there will be different logic to parse a TCG PC Client event log, a Canonical Event Log, and a Confidential Computing Event Log (CCEL). Furthermore, different technologies use different types/number of measurement registers. 39 | 40 | ## Event Parsing 41 | Event parsing is the process of pulling information from the events. The verifier uses the output to make verification decisions against the appraisal policy, endorsements, and reference values. Since parsing events securely often requires examining state transitions from other events, this is also somewhat technology specific. For example, the ExitBootServices transition is measured in PCR5 on TPMs and RTMR0 on Intel TDX. 42 | 43 | Some examples of useful measurement information: 44 | * Firmware 45 | * Secure Boot configuration 46 | * Bootloaders 47 | * GRUB is supported by this library 48 | * Kernel 49 | * Command line 50 | * Initramfs 51 | * Integrity Measurement Architecture 52 | * Not supported 53 | 54 | ## Reference Measurements 55 | This library does not vend reference measurements or any specific Reference Integrity Manifests (RIMs). It is the responsibility of integrators to supply or fetch appropriate reference measurements. 56 | 57 | # Disclaimers 58 | This repo is part of a larger [go-attestation](https://github.com/google/go-attestation) migration. 59 | Expect pre-release commits and even v0.* releases to have plenty of breaking changes. 60 | 61 | This is not an officially supported Google product. 62 | -------------------------------------------------------------------------------- /register/pcr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package register contains measurement register-specific implementations. 16 | package register 17 | 18 | import ( 19 | "crypto" 20 | "fmt" 21 | 22 | pb "github.com/google/go-eventlog/proto/state" 23 | "github.com/google/go-tpm/legacy/tpm2" 24 | ) 25 | 26 | // PCRBank is a bank of PCRs that all correspond to the same hash algorithm. 27 | type PCRBank struct { 28 | TCGHashAlgo pb.HashAlgo 29 | PCRs []PCR 30 | } 31 | 32 | // CryptoHash returns the crypto.Hash algorithm related to the PCR bank. 33 | func (b PCRBank) CryptoHash() (crypto.Hash, error) { 34 | cryptoHash, err := b.TCGHashAlgo.CryptoHash() 35 | if err != nil { 36 | return crypto.Hash(0), fmt.Errorf("received a bad PCR bank of type %s: %v", b.TCGHashAlgo, err) 37 | } 38 | var invalidPCRs []int 39 | for _, pcr := range b.PCRs { 40 | if pcr.DgstAlg() != cryptoHash { 41 | invalidPCRs = append(invalidPCRs, pcr.Idx()) 42 | } 43 | } 44 | if len(invalidPCRs) != 0 { 45 | return crypto.Hash(0), fmt.Errorf("found an invalid hash algorithm in PCRs %v for bank of algorithm type %s", invalidPCRs, b.TCGHashAlgo.String()) 46 | } 47 | return cryptoHash, nil 48 | } 49 | 50 | // MRs returns a slice of MR from the PCR implementation. 51 | func (b PCRBank) MRs() []MR { 52 | mrs := make([]MR, len(b.PCRs)) 53 | for i, v := range b.PCRs { 54 | mrs[i] = v 55 | } 56 | return mrs 57 | } 58 | 59 | // PCR encapsulates the value of a PCR at a point in time. 60 | type PCR struct { 61 | Index int 62 | Digest []byte 63 | DigestAlg crypto.Hash 64 | 65 | // quoteVerified is true if the PCR was verified against a quote. 66 | // NOT for use in go-eventlog. 67 | // Included for backcompat with the go-attestation API. 68 | quoteVerified bool 69 | } 70 | 71 | // Idx gives the PCR index. 72 | func (p PCR) Idx() int { 73 | return p.Index 74 | } 75 | 76 | // Dgst gives the PCR digest. 77 | func (p PCR) Dgst() []byte { 78 | return p.Digest 79 | } 80 | 81 | // DgstAlg gives the PCR digest algorithm as a crypto.Hash. 82 | func (p PCR) DgstAlg() crypto.Hash { 83 | return p.DigestAlg 84 | } 85 | 86 | // SetQuoteVerified sets that the quote verified is true. 87 | // NOT for use in go-eventlog. 88 | // Included for backcompat with the go-attestation API. 89 | func (p *PCR) SetQuoteVerified() { 90 | p.quoteVerified = true 91 | } 92 | 93 | // QuoteVerified returns true if the value of this PCR was previously 94 | // verified against a Quote, in a call to AKPublic.Verify or AKPublic.VerifyAll. 95 | // NOT for use in go-eventlog. 96 | // Included for backcompat with the go-attestation API. 97 | func (p *PCR) QuoteVerified() bool { 98 | return p.quoteVerified 99 | } 100 | 101 | // HashAlg identifies a hashing Algorithm. 102 | // Included for backcompat with the go-attestation API. 103 | type HashAlg uint8 104 | 105 | // Valid hash algorithms. 106 | var ( 107 | HashSHA1 = HashAlg(tpm2.AlgSHA1) 108 | HashSHA256 = HashAlg(tpm2.AlgSHA256) 109 | HashSHA384 = HashAlg(tpm2.AlgSHA384) 110 | ) 111 | 112 | // CryptoHash turns the hash algo into a crypto.Hash 113 | func (a HashAlg) CryptoHash() crypto.Hash { 114 | switch a { 115 | case HashSHA1: 116 | return crypto.SHA1 117 | case HashSHA256: 118 | return crypto.SHA256 119 | case HashSHA384: 120 | return crypto.SHA384 121 | } 122 | return 0 123 | } 124 | 125 | // GoTPMAlg returns the go-tpm definition of this crypto.Hash, based on the 126 | // TCG Algorithm Registry. 127 | func (a HashAlg) GoTPMAlg() tpm2.Algorithm { 128 | switch a { 129 | case HashSHA1: 130 | return tpm2.AlgSHA1 131 | case HashSHA256: 132 | return tpm2.AlgSHA256 133 | case HashSHA384: 134 | return tpm2.AlgSHA384 135 | } 136 | return 0 137 | } 138 | 139 | // String returns a human-friendly representation of the hash algorithm. 140 | func (a HashAlg) String() string { 141 | switch a { 142 | case HashSHA1: 143 | return "SHA1" 144 | case HashSHA256: 145 | return "SHA256" 146 | case HashSHA384: 147 | return "SHA384" 148 | } 149 | return fmt.Sprintf("HashAlg<%d>", int(a)) 150 | } 151 | -------------------------------------------------------------------------------- /wellknown/policy_constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package wellknown provides events, event data, other constants, and helper 16 | // functions for parsing event information and enforcing policy decisions. 17 | package wellknown 18 | 19 | import ( 20 | "bytes" 21 | "crypto/x509" 22 | _ "embed" // Necessary to use go:embed 23 | "errors" 24 | "fmt" 25 | "strconv" 26 | 27 | pb "github.com/google/go-eventlog/proto/state" 28 | ) 29 | 30 | // Expected TCG Event Log Event Types. 31 | // 32 | // Taken from TCG PC Client Platform Firmware Profile Specification, 33 | // Table 14 Events. 34 | const ( 35 | NoAction uint32 = 0x00000003 36 | Separator uint32 = 0x00000004 37 | SCRTMVersion uint32 = 0x00000008 38 | IPL uint32 = 0x0000000D 39 | NonhostInfo uint32 = 0x00000011 40 | EFIBootServicesApplication uint32 = 0x80000003 41 | EFIAction uint32 = 0x80000007 42 | ) 43 | 44 | // EventTagLoadedImageHex used with type "EV_EVENT_TAG". 45 | // This corresponds to a TLV struct of type LOAD_OPTIONS_EVENT_TAG_ID (0x8F3B22ED, reversed endian), length 0x1a (26), value `LOADED_IMAGE::LoadOptions\n`. 46 | const EventTagLoadedImageHex = "ed223b8f1a0000004c4f414445445f494d4147453a3a4c6f61644f7074696f6e7300" 47 | 48 | var ( 49 | // GCENonHostInfoSignature identifies the GCE Non-Host info event, which 50 | // indicates if memory encryption is enabled. This event is 32-bytes consisting 51 | // of the below signature (16 bytes), followed by a byte indicating whether 52 | // it is a confidential vm, followed by 15 reserved bytes. 53 | GCENonHostInfoSignature = []byte("GCE NonHostInfo\x00") 54 | // GceVirtualFirmwarePrefix is the little-endian UCS-2 encoded string 55 | // "GCE Virtual Firmware v" without a null terminator. All GCE firmware 56 | // versions are UCS-2 encoded, start with this prefix, contain the firmware 57 | // version encoded as an integer, and end with a null terminator. 58 | GceVirtualFirmwarePrefix = []byte{0x47, 0x00, 0x43, 0x00, 59 | 0x45, 0x00, 0x20, 0x00, 0x56, 0x00, 0x69, 0x00, 0x72, 0x00, 60 | 0x74, 0x00, 0x75, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x20, 0x00, 61 | 0x46, 0x00, 0x69, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x77, 0x00, 62 | 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x76, 0x00} 63 | ) 64 | 65 | // Standard Secure Boot certificates (DER encoded) 66 | var ( 67 | //go:embed secure-boot/GcePk.crt 68 | GceDefaultPKCert []byte 69 | //go:embed secure-boot/MicCorKEKCA2011_2011-06-24.crt 70 | MicrosoftKEKCA2011Cert []byte 71 | //go:embed secure-boot/MicWinProPCA2011_2011-10-19.crt 72 | WindowsProductionPCA2011Cert []byte 73 | //go:embed secure-boot/MicCorUEFCA2011_2011-06-27.crt 74 | MicrosoftUEFICA2011Cert []byte 75 | ) 76 | 77 | // Revoked Signing certificates (DER encoded) 78 | var ( 79 | //go:embed secure-boot/canonical-boothole.crt 80 | RevokedCanonicalBootholeCert []byte 81 | //go:embed secure-boot/debian-boothole.crt 82 | RevokedDebianBootholeCert []byte 83 | //go:embed secure-boot/cisco-boothole.crt 84 | RevokedCiscoCert []byte 85 | ) 86 | 87 | // Certificates corresponding to the known CA certs for GCE. 88 | var ( 89 | GceEKRoots []*x509.Certificate 90 | GceEKIntermediates []*x509.Certificate 91 | ) 92 | 93 | // ConvertSCRTMVersionToGCEFirmwareVersion attempts to parse the Firmware 94 | // Version of a GCE VM from the bytes of the version string of the SCRTM. This 95 | // data should come from a valid and verified EV_S_CRTM_VERSION event. 96 | func ConvertSCRTMVersionToGCEFirmwareVersion(version []byte) (uint32, error) { 97 | prefixLen := len(GceVirtualFirmwarePrefix) 98 | if (len(version) <= prefixLen) || (len(version)%2 != 0) { 99 | return 0, fmt.Errorf("length of GCE version (%d) is invalid", len(version)) 100 | } 101 | if !bytes.Equal(version[:prefixLen], GceVirtualFirmwarePrefix) { 102 | return 0, errors.New("prefix for GCE version is missing") 103 | } 104 | asciiVersion := []byte{} 105 | for i, b := range version[prefixLen:] { 106 | // Skip the UCS-2 null bytes and the null terminator 107 | if b == '\x00' { 108 | continue 109 | } 110 | // All odd bytes in our UCS-2 string should be Null 111 | if i%2 != 0 { 112 | return 0, errors.New("invalid UCS-2 in the version string") 113 | } 114 | asciiVersion = append(asciiVersion, b) 115 | } 116 | 117 | versionNum, err := strconv.Atoi(string(asciiVersion)) 118 | if err != nil { 119 | return 0, fmt.Errorf("when parsing GCE firmware version: %w", err) 120 | } 121 | return uint32(versionNum), nil 122 | } 123 | 124 | // ConvertGCEFirmwareVersionToSCRTMVersion creates the corresponding SCRTM 125 | // version string from a numerical GCE firmware version. The returned string 126 | // is UCS2 encoded with a null terminator. A version of 0 corresponds to an 127 | // empty string (representing old GCE VMs that just used an empty string). 128 | func ConvertGCEFirmwareVersionToSCRTMVersion(version uint32) []byte { 129 | if version == 0 { 130 | return []byte{} 131 | } 132 | versionString := GceVirtualFirmwarePrefix 133 | for _, b := range []byte(strconv.Itoa(int(version))) { 134 | // Convert ACSII to little-endian UCS-2 135 | versionString = append(versionString, b, 0) 136 | } 137 | // Add the null terminator 138 | return append(versionString, 0, 0) 139 | } 140 | 141 | // ParseGCENonHostInfo attempts to parse the Confidential VM 142 | // technology used by a GCE VM from the GCE Non-Host info event. This data 143 | // should come from a valid and verified EV_NONHOST_INFO event. 144 | func ParseGCENonHostInfo(nonHostInfo []byte) (pb.GCEConfidentialTechnology, error) { 145 | prefixLen := len(GCENonHostInfoSignature) 146 | if len(nonHostInfo) < (prefixLen + 1) { 147 | return pb.GCEConfidentialTechnology_NONE, fmt.Errorf("length of GCE Non-Host info (%d) is too short", len(nonHostInfo)) 148 | } 149 | 150 | if !bytes.Equal(nonHostInfo[:prefixLen], GCENonHostInfoSignature) { 151 | return pb.GCEConfidentialTechnology_NONE, errors.New("prefix for GCE Non-Host info is missing") 152 | } 153 | tech := nonHostInfo[prefixLen] 154 | if tech > byte(pb.GCEConfidentialTechnology_AMD_SEV_SNP) { 155 | return pb.GCEConfidentialTechnology_NONE, fmt.Errorf("unknown GCE Confidential Technology: %d", tech) 156 | } 157 | return pb.GCEConfidentialTechnology(tech), nil 158 | } 159 | -------------------------------------------------------------------------------- /proto/state.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package state; 18 | 19 | option go_package = "github.com/google/go-eventlog/proto/state"; 20 | 21 | // Information uniquely identifying a GCE instance. Can be used to create an 22 | // instance URL, which can then be used with GCE APIs. Formatted like: 23 | // https://www.googleapis.com/compute/v1/projects/{project_id}/zones/{zone}/instances/{instance_name} 24 | message GCEInstanceInfo { 25 | string zone = 1; 26 | string project_id = 2; 27 | uint64 project_number = 3; 28 | string instance_name = 4; 29 | uint64 instance_id = 5; 30 | } 31 | 32 | // The type of UEFI firmware log. 33 | enum LogType { 34 | LOG_TYPE_UNDEFINED = 0; 35 | // The log used by EFI_TCG2_PROTOCOL and defined in the TCG PC Client 36 | // Platform Firmware Profile Specification 37 | LOG_TYPE_TCG2 = 1; 38 | // The log used by EFI_CC_MEASUREMENT_PROTOCOL and defined in the UEFI spec: 39 | // https://uefi.org/specs/UEFI/2.10/38_Confidential_Computing.html. 40 | LOG_TYPE_CC = 2; 41 | } 42 | 43 | // Type of hardware technology used to protect this instance 44 | enum GCEConfidentialTechnology { 45 | NONE = 0; 46 | AMD_SEV = 1; 47 | AMD_SEV_ES = 2; 48 | INTEL_TDX = 3; 49 | AMD_SEV_SNP = 4; 50 | } 51 | 52 | // The platform/firmware state for this instance 53 | message PlatformState { 54 | oneof firmware { 55 | // Raw S-CRTM version identifier (EV_S_CRTM_VERSION) 56 | bytes scrtm_version_id = 1; 57 | // Virtual GCE firmware version (parsed from S-CRTM version id) 58 | uint32 gce_version = 2; 59 | } 60 | // Set to NONE on non-GCE instances or non-Confidential Shielded GCE instances 61 | GCEConfidentialTechnology technology = 3; 62 | 63 | // Only set for GCE instances. 64 | // Included for backcompat. go-eventlog should NOT set this field. 65 | GCEInstanceInfo instance_info = 4; 66 | } 67 | 68 | message GrubFile { 69 | // The digest of the file (pulled from the raw event digest). 70 | bytes digest = 1; 71 | // The event data. This is not measured, so it is untrusted. 72 | bytes untrusted_filename = 2; 73 | } 74 | 75 | message GrubState { 76 | // All GRUB-read and measured files, including grub.cfg. 77 | repeated GrubFile files = 1; 78 | // A list of executed GRUB commands and command lines passed to the kernel 79 | // and kernel modules. 80 | repeated string commands = 2; 81 | } 82 | 83 | // The state of the Linux kernel. 84 | // At the moment, parsing LinuxKernelState relies on parsing the GrubState. 85 | // To do so, use ExtractOpts{Loader: GRUB} when calling ParseMachineState. 86 | message LinuxKernelState { 87 | // The kernel command line. 88 | string command_line = 1; 89 | } 90 | 91 | // A parsed event from the source firmware event log. This can be from either 92 | // the firmware TPM event log, the Confidential Computing event log, or any 93 | // other TCG-like event log used by firmware to record its measurements. 94 | message Event { 95 | // The register this event was extended into. Can be PCR, RTMR, etc. 96 | // Named pcr_index for backcompat reasons. 97 | uint32 pcr_index = 1; 98 | // The type of this event. Note that this value is not verified, so it should 99 | // only be used as a hint during event parsing. 100 | uint32 untrusted_type = 2; 101 | // The raw data associated to this event. The meaning of this data is 102 | // specific to the type of the event. 103 | bytes data = 3; 104 | // The event digest actually extended into the TPM. This is often the hash of 105 | // the data field, but in some cases it may have a type-specific calculation. 106 | bytes digest = 4; 107 | // This is true if hash(data) == digest. 108 | bool digest_verified = 5; 109 | } 110 | 111 | // Common, publicly-listed certificates by different vendors. 112 | enum WellKnownCertificate { 113 | UNKNOWN = 0; 114 | 115 | // Microsoft certs: 116 | // https://go.microsoft.com/fwlink/p/?linkid=321192 117 | MS_WINDOWS_PROD_PCA_2011 = 1; 118 | // https://go.microsoft.com/fwlink/p/?linkid=321194 119 | MS_THIRD_PARTY_UEFI_CA_2011 = 2; 120 | // https://go.microsoft.com/fwlink/p/?linkid=321185 121 | MS_THIRD_PARTY_KEK_CA_2011 = 3; 122 | 123 | // GCE certs: 124 | GCE_DEFAULT_PK = 4; 125 | } 126 | 127 | message Certificate { 128 | // The representation of the certificate. If the certificate matches a 129 | // well-known certificate above, representation should contain the value in 130 | // the enum. Otherwise, it will contain the raw DER. 131 | oneof representation { 132 | // DER representation of the certificate. 133 | bytes der = 1; 134 | WellKnownCertificate well_known = 2; 135 | } 136 | } 137 | 138 | // A Secure Boot database containing lists of hashes and certificates, 139 | // as defined by section 32.4.1 Signature Database in the UEFI spec. 140 | message Database { 141 | repeated Certificate certs = 1; 142 | repeated bytes hashes = 2; 143 | } 144 | 145 | // The Secure Boot state for this instance. 146 | message SecureBootState { 147 | // Whether Secure Boot is enabled. 148 | bool enabled = 1; 149 | // The Secure Boot signature (allowed) database. 150 | Database db = 2; 151 | // The Secure Boot revoked signature (forbidden) database. 152 | Database dbx = 3; 153 | // Authority events post-separator. Pre-separator authorities 154 | // are currently not supported. 155 | Database authority = 4; 156 | // The Secure Boot Platform key, used to sign key exchange keys. 157 | Database pk = 5; 158 | // The Secure Boot Key Exchange Keys, used to sign db and dbx updates. 159 | Database kek = 6; 160 | } 161 | 162 | message EfiApp { 163 | // The PE/COFF digest of the EFI application (pulled from the raw event digest). 164 | // This can also represent digest of the EFI boot/runtime service drivers. 165 | bytes digest = 1; 166 | } 167 | 168 | // The verified state of EFI Drivers and Applications. Policy usage on this machine state 169 | // should check the entire set of EFI App digests matches, not a subset. 170 | message EfiState { 171 | // UEFI's OS Loader code is required to measure attempts to load and execute 172 | // UEFI applications. 173 | // UEFI applications are typically bootloaders such as shim and GRUB. 174 | // These run and are measured using the UEFI LoadImage() service. 175 | repeated EfiApp apps = 1; 176 | // The EFI drivers, 177 | // obtained from https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf#page=22. 178 | // The EFI Boot Services Drivers from adapter or loaded bydriver in adapter. 179 | repeated EfiApp boot_services_drivers = 2; 180 | // The EFI Runtime Drivers from adapter or loaded bydriver in adapter. 181 | repeated EfiApp runtime_services_drivers = 3; 182 | } 183 | 184 | // Enum values come from the TCG Algorithm Registry - v1.27 - Table 3. 185 | enum HashAlgo { 186 | HASH_INVALID = 0x0000; 187 | SHA1 = 0x0004; 188 | SHA256 = 0x000B; 189 | SHA384 = 0x000C; 190 | SHA512 = 0x000D; 191 | } 192 | 193 | // The verified state of a booted machine, obtained from a UEFI event log. 194 | // The state is extracted from either EFI_TCG2_PROTOCOL or 195 | // EFI_CC_MEASUREMENT_PROTOCOL. Both of these follow the TCG-defined format 196 | // in https://trustedcomputinggroup.org/resource/tcg-efi-protocol-specification/ 197 | // The TCG2-related (TPM) logs are structured using TCG_PCR_EVENT (SHA1 format) 198 | // or TCG_PCR_EVENT2 (Crypto Agile format). 199 | // The CC logs are structured using CC_EVENT. 200 | message FirmwareLogState { 201 | reserved 7; 202 | 203 | PlatformState platform = 1; 204 | 205 | SecureBootState secure_boot = 2; 206 | 207 | // The complete parsed Firmware Event Log, including those events used to 208 | // create this MachineState. 209 | repeated Event raw_events = 3; 210 | 211 | // The hash algorithm used to calculate event digests to verify a log entry. 212 | HashAlgo hash = 4; 213 | 214 | GrubState grub = 5; 215 | 216 | LinuxKernelState linux_kernel = 6; 217 | 218 | EfiState efi = 8; 219 | 220 | LogType log_type = 9; 221 | } 222 | 223 | -------------------------------------------------------------------------------- /cel/canonical_eventlog_test.go: -------------------------------------------------------------------------------- 1 | package cel 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/rand" 7 | "fmt" 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/google/go-eventlog/register" 12 | ) 13 | 14 | var measuredHashes = []crypto.Hash{crypto.SHA1, crypto.SHA256} 15 | 16 | func TestCELEncodingDecoding(t *testing.T) { 17 | rot, err := register.CreateFakeRot(measuredHashes, 24) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | tests := []MRType{PCRType, CCMRType} 22 | 23 | for _, tc := range tests { 24 | t.Run(fmt.Sprintf("MRType %v", tc), func(t *testing.T) { 25 | cel := eventLog{Type: tc} 26 | 27 | fakeEvent1 := FakeTlv{FakeEvent1, []byte("docker.io/bazel/experimental/test:latest")} 28 | appendFakeMREventOrFatal(t, &cel, rot, 16, measuredHashes, fakeEvent1) 29 | 30 | fakeEvent2 := FakeTlv{FakeEvent2, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")} 31 | appendFakeMREventOrFatal(t, &cel, rot, 23, measuredHashes, fakeEvent2) 32 | 33 | var buf bytes.Buffer 34 | if err := cel.EncodeCEL(&buf); err != nil { 35 | t.Fatal(err) 36 | } 37 | decodedcel, err := DecodeToCEL(&buf) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | if decodedcel.MRType() != tc { 42 | t.Errorf("decoded CEL MR type: got %v, want %v", decodedcel.MRType(), tc) 43 | } 44 | if len(decodedcel.Records()) != 2 { 45 | t.Errorf("should have two records") 46 | } 47 | if decodedcel.Records()[0].RecNum != 0 { 48 | t.Errorf("recnum mismatch") 49 | } 50 | if decodedcel.Records()[1].RecNum != 1 { 51 | t.Errorf("recnum mismatch") 52 | } 53 | if decodedcel.Records()[0].IndexType != tc { 54 | t.Errorf("index type mismatch") 55 | } 56 | if decodedcel.Records()[0].Index != uint8(16) { 57 | t.Errorf("pcr value mismatch") 58 | } 59 | if decodedcel.Records()[1].IndexType != tc { 60 | t.Errorf("index type mismatch") 61 | } 62 | if decodedcel.Records()[1].Index != uint8(23) { 63 | t.Errorf("pcr value mismatch") 64 | } 65 | 66 | if !reflect.DeepEqual(decodedcel.Records(), cel.Records()) { 67 | t.Errorf("decoded CEL doesn't equal to the original one") 68 | } 69 | }) 70 | } 71 | } 72 | 73 | func TestCELAppendDifferentMRTypes(t *testing.T) { 74 | rot, err := register.CreateFakeRot(measuredHashes, 24) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | tests := []MRType{PCRType, CCMRType} 80 | 81 | for _, tc := range tests { 82 | t.Run(fmt.Sprintf("MRType %v", tc), func(t *testing.T) { 83 | el := eventLog{Type: tc} 84 | event := FakeTlv{FakeEvent1, []byte("hellothere")} 85 | 86 | appendFakeMREventOrFatal(t, &el, rot, 8, measuredHashes, event) 87 | appendFakeMREventOrFatal(t, &el, rot, 8, measuredHashes, event) 88 | appendFakeMREventOrFatal(t, &el, rot, 8, measuredHashes, event) 89 | appendFakeMREventOrFatal(t, &el, rot, 8, measuredHashes, event) 90 | appendFakeMREventOrFatal(t, &el, rot, 8, measuredHashes, event) 91 | 92 | for _, rec := range el.Records() { 93 | if rec.IndexType != tc { 94 | t.Errorf("AppendEvent(): got Index Type %v, want type %v", rec.IndexType, tc) 95 | } 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func TestCELMeasureAndReplay(t *testing.T) { 102 | rot, err := register.CreateFakeRot(measuredHashes, 24) 103 | if err != nil { 104 | t.Fatal(err) 105 | } 106 | 107 | cel := NewPCR() 108 | event := FakeTlv{FakeEvent1, []byte("docker.io/bazel/experimental/test:latest")} 109 | 110 | someEvent2 := make([]byte, 10) 111 | rand.Read(someEvent2) 112 | FakeEvent2 := FakeTlv{FakeEvent2, someEvent2} 113 | 114 | appendFakeMREventOrFatal(t, cel, rot, 12, measuredHashes, event) 115 | appendFakeMREventOrFatal(t, cel, rot, 12, measuredHashes, FakeEvent2) 116 | 117 | appendFakeMREventOrFatal(t, cel, rot, 18, measuredHashes, FakeEvent2) 118 | appendFakeMREventOrFatal(t, cel, rot, 18, measuredHashes, event) 119 | appendFakeMREventOrFatal(t, cel, rot, 18, measuredHashes, event) 120 | 121 | replay(t, cel, rot, measuredHashes, 122 | []int{12, 18}, true /*shouldSucceed*/) 123 | // Supersets should pass. 124 | replay(t, cel, rot, measuredHashes, 125 | []int{0, 12, 13, 14, 18, 19, 22, 23}, true /*shouldSucceed*/) 126 | } 127 | 128 | func TestCELReplayFailTamperedDigest(t *testing.T) { 129 | rot, err := register.CreateFakeRot(measuredHashes, 24) 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | cel := NewPCR() 134 | 135 | event := FakeTlv{FakeEvent1, []byte("docker.io/bazel/experimental/test:latest")} 136 | someEvent2 := make([]byte, 10) 137 | rand.Read(someEvent2) 138 | FakeEvent2 := FakeTlv{FakeEvent2, someEvent2} 139 | 140 | appendFakeMREventOrFatal(t, cel, rot, 2, measuredHashes, event) 141 | appendFakeMREventOrFatal(t, cel, rot, 2, measuredHashes, FakeEvent2) 142 | appendFakeMREventOrFatal(t, cel, rot, 3, measuredHashes, FakeEvent2) 143 | appendFakeMREventOrFatal(t, cel, rot, 3, measuredHashes, event) 144 | appendFakeMREventOrFatal(t, cel, rot, 3, measuredHashes, event) 145 | 146 | modifiedRecord := cel.Records()[3] 147 | for hash := range modifiedRecord.Digests { 148 | newDigest := make([]byte, hash.Size()) 149 | rand.Read(newDigest) 150 | modifiedRecord.Digests[hash] = newDigest 151 | } 152 | replay(t, cel, rot, measuredHashes, 153 | []int{2, 3}, false /*shouldSucceed*/) 154 | } 155 | 156 | func TestCELReplayEmpty(t *testing.T) { 157 | rot, err := register.CreateFakeRot(measuredHashes, 24) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | cel := NewPCR() 162 | replay(t, cel, rot, []crypto.Hash{crypto.SHA1, crypto.SHA256}, 163 | []int{12, 13}, true /*shouldSucceed*/) 164 | } 165 | 166 | func TestCELReplayFailMissingMRsInBank(t *testing.T) { 167 | rot, err := register.CreateFakeRot(measuredHashes, 24) 168 | if err != nil { 169 | t.Fatal(err) 170 | } 171 | cel := &eventLog{Type: PCRType} 172 | 173 | someEvent := make([]byte, 10) 174 | someEvent2 := make([]byte, 10) 175 | rand.Read(someEvent2) 176 | 177 | appendFakeMREventOrFatal(t, cel, rot, 7, measuredHashes, FakeTlv{FakeEvent1, someEvent}) 178 | appendFakeMREventOrFatal(t, cel, rot, 8, measuredHashes, FakeTlv{FakeEvent2, someEvent2}) 179 | 180 | replay(t, cel, rot, measuredHashes, 181 | []int{7}, false /*shouldSucceed*/) 182 | replay(t, cel, rot, measuredHashes, 183 | []int{8}, false /*shouldSucceed*/) 184 | } 185 | 186 | func TestDecodeCELFailBadMRTypes(t *testing.T) { 187 | rot, err := register.CreateFakeRot(measuredHashes, 24) 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | cel := &eventLog{} 192 | someEvent := make([]byte, 10) 193 | if err := cel.AppendEvent(FakeTlv{FakeEvent1, someEvent}, measuredHashes, 7, fakeRotExtender(rot)); err == nil { 194 | t.Errorf("AppendEvent(UnsetMR): got %v, expect err", err) 195 | } 196 | 197 | } 198 | 199 | func TestCELAppendFailBadMRType(t *testing.T) { 200 | rot, err := register.CreateFakeRot(measuredHashes, 24) 201 | if err != nil { 202 | t.Fatal(err) 203 | } 204 | 205 | tests := []struct { 206 | mrT MRType 207 | expectErr bool 208 | }{ 209 | {mrT: PCRType, expectErr: false}, 210 | {mrT: CCMRType, expectErr: false}, 211 | {mrT: 0, expectErr: true}, 212 | {mrT: 2, expectErr: true}, 213 | {mrT: 4, expectErr: true}, 214 | {mrT: 100, expectErr: true}, 215 | {mrT: 100, expectErr: true}, 216 | {mrT: 255, expectErr: true}, 217 | } 218 | 219 | for _, tc := range tests { 220 | t.Run(fmt.Sprintf("MRType %v", tc.mrT), func(t *testing.T) { 221 | cel := &eventLog{Type: tc.mrT} 222 | someEvent := make([]byte, 10) 223 | if err := cel.AppendEvent(FakeTlv{FakeEvent1, someEvent}, measuredHashes, 7, fakeRotExtender(rot)); (err != nil) != tc.expectErr { 224 | t.Errorf("AppendEvent(MRType %v): got %v, expectErr %v", tc.mrT, err, tc.expectErr) 225 | } 226 | }) 227 | } 228 | } 229 | 230 | func replay(t *testing.T, cel CEL, rot register.FakeROT, measuredHashes []crypto.Hash, mrs []int, shouldSucceed bool) { 231 | for _, hash := range measuredHashes { 232 | bank, err := rot.ReadMRs(hash, mrs) 233 | if err != nil { 234 | t.Fatal(err) 235 | } 236 | if err := cel.Replay(bank); shouldSucceed && err != nil { 237 | t.Errorf("failed to replay CEL on %v bank: %v", 238 | hash, err) 239 | } 240 | } 241 | } 242 | 243 | func appendFakeMREventOrFatal(t *testing.T, cel CEL, fakeROT register.FakeROT, mrIndex int, banks []crypto.Hash, event Content) { 244 | if err := cel.AppendEvent(event, banks, mrIndex, fakeRotExtender(fakeROT)); err != nil { 245 | t.Fatalf("failed to append PCR event: %v", err) 246 | } 247 | } 248 | 249 | func fakeRotExtender(rot register.FakeROT) MRExtender { 250 | return func(bank crypto.Hash, mrIdx int, digest []byte) error { 251 | return rot.ExtendMR(register.FakeMR{ 252 | Index: mrIdx, 253 | Digest: digest, 254 | DigestAlg: bank, 255 | }) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /ccel/cceventlog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package ccel 16 | 17 | import ( 18 | "os" 19 | "strconv" 20 | "testing" 21 | 22 | "github.com/google/go-cmp/cmp" 23 | "github.com/google/go-eventlog/register" 24 | "github.com/google/go-eventlog/tcg" 25 | ) 26 | 27 | type eventLog struct { 28 | fname string 29 | mrs []register.MR 30 | // TODO: migrate off of the slice based bank type and move to a map-based representation. 31 | rtmrs []register.RTMR 32 | } 33 | 34 | var COS113TDX = eventLog{ 35 | fname: "../testdata/eventlogs/ccel/cos-113-intel-tdx.bin", 36 | mrs: []register.MR{ 37 | register.RTMR{ 38 | Index: 0, 39 | 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"), 40 | }, 41 | register.RTMR{ 42 | Index: 1, 43 | 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"), 44 | }, 45 | register.RTMR{ 46 | Index: 2, 47 | 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"), 48 | }, 49 | }, 50 | rtmrs: []register.RTMR{ 51 | { 52 | Index: 0, 53 | 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"), 54 | }, 55 | { 56 | Index: 1, 57 | 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"), 58 | }, 59 | { 60 | Index: 2, 61 | 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"), 62 | }, 63 | }, 64 | } 65 | 66 | var COS113TDXUnpadded = eventLog{ 67 | fname: "../testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator-unpadded.bin", 68 | mrs: []register.MR{ 69 | register.RTMR{ 70 | Index: 0, 71 | 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-"), 72 | }, 73 | register.RTMR{ 74 | Index: 1, 75 | 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 "), 76 | }, 77 | register.RTMR{ 78 | Index: 2, 79 | 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"), 80 | }, 81 | }, 82 | rtmrs: []register.RTMR{ 83 | { 84 | Index: 0, 85 | 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-"), 86 | }, 87 | { 88 | Index: 1, 89 | 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 "), 90 | }, 91 | { 92 | Index: 2, 93 | 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"), 94 | }, 95 | }, 96 | } 97 | 98 | var COS113TDXPadded = eventLog{ 99 | fname: "../testdata/eventlogs/ccel/cos-113-intel-tdx-dupe-separator.bin", 100 | mrs: []register.MR{ 101 | register.RTMR{ 102 | Index: 0, 103 | 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-"), 104 | }, 105 | register.RTMR{ 106 | Index: 1, 107 | 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 "), 108 | }, 109 | register.RTMR{ 110 | Index: 2, 111 | 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"), 112 | }, 113 | }, 114 | rtmrs: []register.RTMR{ 115 | { 116 | Index: 0, 117 | 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-"), 118 | }, 119 | { 120 | Index: 1, 121 | 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 "), 122 | }, 123 | { 124 | Index: 2, 125 | 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"), 126 | }, 127 | }, 128 | } 129 | 130 | var IntelTestCCEL = eventLog{ 131 | fname: "../testdata/eventlogs/ccel/CCEL.data.bin", 132 | mrs: []register.MR{ 133 | register.RTMR{ 134 | Index: 0, 135 | 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"), 136 | }, 137 | register.RTMR{ 138 | Index: 1, 139 | 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"), 140 | }, 141 | register.RTMR{ 142 | Index: 2, 143 | 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"), 144 | }, 145 | }, 146 | rtmrs: []register.RTMR{ 147 | { 148 | Index: 0, 149 | 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"), 150 | }, 151 | { 152 | Index: 1, 153 | 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"), 154 | }, 155 | { 156 | Index: 2, 157 | 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"), 158 | }, 159 | }, 160 | } 161 | 162 | var GDCCCEL = eventLog{ 163 | fname: "../testdata/eventlogs/ccel/gdc-tdx.bin", 164 | mrs: []register.MR{ 165 | register.RTMR{ 166 | Index: 0, 167 | 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")}, 168 | register.RTMR{ 169 | Index: 1, 170 | 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")}, 171 | register.RTMR{ 172 | Index: 2, 173 | 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")}, 174 | }, 175 | rtmrs: []register.RTMR{ 176 | { 177 | Index: 0, 178 | 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")}, 179 | { 180 | Index: 1, 181 | 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")}, 182 | { 183 | Index: 2, 184 | 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")}, 185 | }, 186 | } 187 | 188 | func TestParseAndReplay(t *testing.T) { 189 | tests := []struct { 190 | el eventLog 191 | allowPadding bool 192 | wantErr bool 193 | }{ 194 | { 195 | el: COS113TDXUnpadded, 196 | allowPadding: true, 197 | wantErr: false, 198 | }, 199 | { 200 | el: COS113TDXUnpadded, 201 | allowPadding: false, 202 | wantErr: false, 203 | }, 204 | { 205 | el: COS113TDXPadded, 206 | allowPadding: true, 207 | wantErr: false, 208 | }, 209 | { 210 | el: COS113TDXPadded, 211 | allowPadding: false, 212 | wantErr: true, 213 | }, 214 | { 215 | el: GDCCCEL, 216 | allowPadding: true, 217 | wantErr: false, 218 | }, 219 | { 220 | el: GDCCCEL, 221 | allowPadding: false, 222 | wantErr: true, 223 | }, 224 | } 225 | for _, tt := range tests { 226 | t.Run(tt.el.fname+"_allowPadding_"+strconv.FormatBool(tt.allowPadding), func(t *testing.T) { 227 | elBytes, err := os.ReadFile(tt.el.fname) 228 | if err != nil { 229 | t.Fatal(err) 230 | } 231 | _, err = tcg.ParseAndReplay(elBytes, 232 | tt.el.mrs, 233 | tcg.ParseOpts{AllowPadding: tt.allowPadding}, 234 | ) 235 | if (err != nil) != tt.wantErr { 236 | t.Errorf("tcg.ParseAndReplay() = %v, wantErr = %v", err, tt.wantErr) 237 | } 238 | }) 239 | } 240 | } 241 | 242 | func TestParseCCACPITable(t *testing.T) { 243 | tableBytes, err := os.ReadFile("../testdata/eventlogs/ccel/CCEL.bin") 244 | if err != nil { 245 | t.Fatal(err) 246 | } 247 | tests := []struct { 248 | name string 249 | table []byte 250 | wantErr bool 251 | wantTable CCACPITable 252 | }{ 253 | { 254 | name: "Happy Path", 255 | table: tableBytes, 256 | wantErr: false, 257 | wantTable: CCACPITable{65536, TDX}, 258 | }, 259 | { 260 | name: "Bad signature", 261 | table: []byte{'A', 'B', 'C', 'D', 56, 1, 2, 3, 4}, 262 | wantErr: true, 263 | wantTable: CCACPITable{}, 264 | }, 265 | { 266 | name: "Bad length", 267 | table: []byte{'C', 'C', 'E', 'L', 48, 0, 0, 0}, 268 | wantErr: true, 269 | wantTable: CCACPITable{}, 270 | }, 271 | } 272 | for _, tt := range tests { 273 | t.Run(tt.name, func(t *testing.T) { 274 | acpiTable, err := parseCCELACPITable(tt.table) 275 | if (err != nil) != tt.wantErr { 276 | t.Errorf("parseCCELACPITable() = %v, wantErr %v", err, tt.wantErr) 277 | } else { 278 | if diff := cmp.Diff(acpiTable, tt.wantTable); diff != "" { 279 | t.Errorf("parseCCELACPITable() = %v, want = %v", acpiTable, tt.wantTable) 280 | } 281 | } 282 | }) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /tcg/eventlog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package tcg 16 | 17 | import ( 18 | "bytes" 19 | "encoding/base64" 20 | "encoding/json" 21 | "os" 22 | "testing" 23 | 24 | "github.com/google/go-eventlog/internal/testutil" 25 | "github.com/google/go-eventlog/register" 26 | "github.com/google/go-tpm/legacy/tpm2" 27 | ) 28 | 29 | func TestParseEventLogWindows(t *testing.T) { 30 | testParseEventLog(t, "../testdata/legacydata/windows_gcp_shielded_vm.json") 31 | } 32 | 33 | func TestParseEventLogLinux(t *testing.T) { 34 | testParseEventLog(t, "../testdata/legacydata/linux_tpm12.json") 35 | } 36 | 37 | func testParseEventLog(t *testing.T, testdata string) { 38 | data, err := os.ReadFile(testdata) 39 | if err != nil { 40 | t.Fatalf("reading test data: %v", err) 41 | } 42 | var dump testutil.Dump 43 | if err := json.Unmarshal(data, &dump); err != nil { 44 | t.Fatalf("parsing test data: %v", err) 45 | } 46 | if _, err := ParseEventLog(dump.Log.Raw, ParseOpts{}); err != nil { 47 | t.Fatalf("parsing event log: %v", err) 48 | } 49 | } 50 | 51 | func TestParseCryptoAgileEventLog(t *testing.T) { 52 | data, err := os.ReadFile("../testdata/legacydata/crypto_agile_eventlog") 53 | if err != nil { 54 | t.Fatalf("reading test data: %v", err) 55 | } 56 | if _, err := ParseEventLog(data, ParseOpts{}); err != nil { 57 | t.Fatalf("parsing event log: %v", err) 58 | } 59 | } 60 | 61 | func TestEventLogLinux(t *testing.T) { 62 | testEventLog(t, "../testdata/legacydata/linux_tpm12.json") 63 | } 64 | 65 | func TestEventLog(t *testing.T) { 66 | testEventLog(t, "../testdata/legacydata/windows_gcp_shielded_vm.json") 67 | } 68 | 69 | func testEventLog(t *testing.T, testdata string) { 70 | data, err := os.ReadFile(testdata) 71 | if err != nil { 72 | t.Fatalf("reading test data: %v", err) 73 | } 74 | var dump testutil.Dump 75 | if err := json.Unmarshal(data, &dump); err != nil { 76 | t.Fatalf("parsing test data: %v", err) 77 | } 78 | 79 | el, err := ParseEventLog(dump.Log.Raw, ParseOpts{}) 80 | if err != nil { 81 | t.Fatalf("parsing event log: %v", err) 82 | } 83 | events, err := el.Verify(convertToMRs(dump.Log.PCRs)) 84 | if err != nil { 85 | t.Fatalf("validating event log: %v", err) 86 | } 87 | 88 | for i, e := range events { 89 | if e.sequence != i { 90 | t.Errorf("event out of order: events[%d].sequence = %d, want %d", i, e.sequence, i) 91 | } 92 | } 93 | } 94 | 95 | func convertToMRs(pcrs []register.PCR) []register.MR { 96 | mrs := make([]register.MR, len(pcrs)) 97 | for i, v := range pcrs { 98 | mrs[i] = v 99 | } 100 | return mrs 101 | } 102 | func TestParseEventLogEventSizeTooLarge(t *testing.T) { 103 | data := []byte{ 104 | // PCR index 105 | 0x30, 0x34, 0x39, 0x33, 106 | // type 107 | 0x36, 0x30, 0x30, 0x32, 108 | 109 | // Digest 110 | 0x31, 0x39, 0x36, 0x33, 0x39, 0x34, 0x34, 0x37, 0x39, 0x32, 111 | 0x31, 0x32, 0x32, 0x37, 0x39, 0x30, 0x34, 0x30, 0x31, 0x6d, 112 | 113 | // Event size (3.183 GB) 114 | 0xbd, 0xbf, 0xef, 0x47, 115 | 116 | // "event data" 117 | 0x00, 0x00, 0x00, 0x00, 118 | } 119 | 120 | // If this doesn't panic, the test passed 121 | // TODO(ericchiang): use errors.As once go-attestation switches to Go 1.13. 122 | _, err := ParseEventLog(data, ParseOpts{}) 123 | if err == nil { 124 | t.Fatalf("expected parsing invalid event log to fail") 125 | } 126 | } 127 | 128 | func TestParseEventLogEventSizeZero(t *testing.T) { 129 | data := []byte{ 130 | // PCR index 131 | 0x4, 0x0, 0x0, 0x0, 132 | 133 | // type 134 | 0xd, 0x0, 0x0, 0x0, 135 | 136 | // Digest 137 | 0x94, 0x2d, 0xb7, 0x4a, 0xa7, 0x37, 0x5b, 0x23, 0xea, 0x23, 138 | 0x58, 0xeb, 0x3b, 0x31, 0x59, 0x88, 0x60, 0xf6, 0x90, 0x59, 139 | 140 | // Event size (0 B) 141 | 0x0, 0x0, 0x0, 0x0, 142 | 143 | // no "event data" 144 | } 145 | 146 | if _, err := parseRawEvent(bytes.NewBuffer(data), nil); err != nil { 147 | t.Fatalf("parsing event log: %v", err) 148 | } 149 | } 150 | 151 | func TestParseEventLog2EventSizeZero(t *testing.T) { 152 | data := []byte{ 153 | // PCR index 154 | 0x0, 0x0, 0x0, 0x0, 155 | 156 | // type 157 | 0x7, 0x0, 0x0, 0x0, 158 | 159 | // number of digests 160 | 0x1, 0x0, 0x0, 0x0, 161 | 162 | // algorithm 163 | 0xb, 0x0, 164 | 165 | // Digest 166 | 0xc8, 0xe3, 0x88, 0xb4, 0x79, 0x12, 0x86, 0x0c, 167 | 0x66, 0xa1, 0x5d, 0xad, 0xc4, 0x34, 0xf5, 0xdf, 168 | 0x73, 0x6c, 0x3a, 0xb4, 0xbe, 0x52, 0x07, 0x08, 169 | 0xdf, 0xac, 0x48, 0x2d, 0x71, 0xce, 0xa0, 0x73, 170 | 171 | // Event size (0 B) 172 | 0x0, 0x0, 0x0, 0x0, 173 | 174 | // no "event data" 175 | } 176 | 177 | specID := &specIDEvent{ 178 | algs: []specAlgSize{ 179 | {ID: uint16(tpm2.AlgSHA256), Size: 32}, 180 | }, 181 | } 182 | 183 | if _, err := parseRawEvent2(bytes.NewBuffer(data), specID); err != nil { 184 | t.Fatalf("parsing event log: %v", err) 185 | } 186 | } 187 | 188 | func TestParseShortNoAction(t *testing.T) { 189 | // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110 190 | // says: "For EV_NO_ACTION events other than the EFI Specification ID event 191 | // (Section 9.4.5.1) the log will ...". Thus it is concluded other 192 | // than "EFI Specification ID" events are also valid as NO_ACTION events. 193 | // 194 | // Currently we just assume that such events will have Data shorter than 195 | // "EFI Specification ID" field. 196 | 197 | data, err := os.ReadFile("../testdata/legacydata/short_no_action_eventlog") 198 | if err != nil { 199 | t.Fatalf("reading test data: %v", err) 200 | } 201 | if _, err := ParseEventLog(data, ParseOpts{}); err != nil { 202 | t.Fatalf("parsing event log: %v", err) 203 | } 204 | } 205 | 206 | func TestParseSpecIDEvent(t *testing.T) { 207 | tests := []struct { 208 | name string 209 | data []byte 210 | want []uint16 211 | wantErr bool 212 | }{ 213 | { 214 | name: "sha1", 215 | data: append( 216 | []byte("Spec ID Event03"), 0x0, 217 | 0x0, 0x0, 0x0, 0x0, // platform class 218 | 0x0, // version minor 219 | 0x2, // version major 220 | 0x0, // errata 221 | 0x8, // uintn size 222 | 0x1, 0x0, 0x0, 0x0, // num algs 223 | 0x04, 0x0, // SHA1 224 | 0x14, 0x0, // size 225 | 0x2, // vendor info size 226 | 0x0, 0x0, 227 | ), 228 | want: []uint16{0x0004}, 229 | }, 230 | { 231 | name: "sha1_and_sha256", 232 | data: append( 233 | []byte("Spec ID Event03"), 0x0, 234 | 0x0, 0x0, 0x0, 0x0, // platform class 235 | 0x0, // version minor 236 | 0x2, // version major 237 | 0x0, // errata 238 | 0x8, // uintn size 239 | 0x2, 0x0, 0x0, 0x0, // num algs 240 | 0x04, 0x0, // SHA1 241 | 0x14, 0x0, // size 242 | 0x0B, 0x0, // SHA256 243 | 0x20, 0x0, // size 244 | 0x2, // vendor info size 245 | 0x0, 0x0, 246 | ), 247 | want: []uint16{0x0004, 0x000B}, 248 | }, 249 | { 250 | name: "invalid_version", 251 | data: append( 252 | []byte("Spec ID Event03"), 0x0, 253 | 0x0, 0x0, 0x0, 0x0, // platform class 254 | 0x2, // version minor 255 | 0x1, // version major 256 | 0x0, // errata 257 | 0x8, // uintn size 258 | 0x2, 0x0, 0x0, 0x0, // num algs 259 | 0x04, 0x0, // SHA1 260 | 0x14, 0x0, // size 261 | 0x0B, 0x0, // SHA256 262 | 0x20, 0x0, // size 263 | 0x2, // vendor info size 264 | 0x0, 0x0, 265 | ), 266 | wantErr: true, 267 | }, 268 | { 269 | name: "malicious_number_of_algs", 270 | data: append( 271 | []byte("Spec ID Event03"), 0x0, 272 | 0x0, 0x0, 0x0, 0x0, // platform class 273 | 0x0, // version minor 274 | 0x2, // version major 275 | 0x0, // errata 276 | 0x8, // uintn size 277 | 0xff, 0xff, 0xff, 0xff, // num algs 278 | 0x04, 0x0, // SHA1 279 | 0x14, 0x0, // size 280 | 0x2, // vendor info size 281 | 0x0, 0x0, 282 | ), 283 | wantErr: true, 284 | }, 285 | } 286 | for _, test := range tests { 287 | t.Run(test.name, func(t *testing.T) { 288 | spec, err := parseSpecIDEvent(test.data) 289 | var algs []uint16 290 | if (err != nil) != test.wantErr { 291 | t.Fatalf("parsing spec, wantErr=%t, got=%v", test.wantErr, err) 292 | } 293 | if err != nil { 294 | return 295 | } 296 | algsEq := func(want, got []uint16) bool { 297 | if len(got) != len(want) { 298 | return false 299 | } 300 | for i, alg := range got { 301 | if want[i] != alg { 302 | return false 303 | } 304 | } 305 | return true 306 | } 307 | 308 | for _, alg := range spec.algs { 309 | algs = append(algs, alg.ID) 310 | } 311 | 312 | if !algsEq(test.want, algs) { 313 | t.Errorf("algorithms, got=%x, want=%x", spec.algs, test.want) 314 | } 315 | }) 316 | } 317 | } 318 | 319 | func TestEBSVerifyWorkaround(t *testing.T) { 320 | pcr5 := []register.MR{ 321 | register.PCR{ 322 | Index: 5, 323 | Digest: []byte{ 324 | 0x31, 0x24, 0x58, 0x08, 0xd6, 0xd3, 0x58, 0x49, 0xbc, 0x39, 325 | 0x4f, 0x63, 0x43, 0xf2, 0xb3, 0xff, 0x90, 0x8e, 0xd5, 0xe3, 326 | }, 327 | DigestAlg: register.HashSHA1.CryptoHash(), 328 | }, 329 | register.PCR{ 330 | Index: 5, 331 | Digest: []byte{ 332 | 0x6c, 0xae, 0xa1, 0x23, 0xfa, 0x61, 0x11, 0x30, 0x5e, 0xe6, 0x24, 333 | 0xe4, 0x52, 0xe2, 0x69, 0xad, 0x14, 0xac, 0x52, 0x2a, 0xb8, 0xbf, 334 | 0x0c, 0x88, 0xe1, 0x16, 0x16, 0xde, 0x4c, 0x22, 0x2f, 0x7d, 335 | }, 336 | DigestAlg: register.HashSHA256.CryptoHash(), 337 | }, 338 | } 339 | 340 | elr, err := os.ReadFile("../testdata/legacydata/ebs_event_missing_eventlog") 341 | if err != nil { 342 | t.Fatal(err) 343 | } 344 | el, err := ParseEventLog(elr, ParseOpts{}) 345 | if err != nil { 346 | t.Fatalf("ParseEventLog() failed: %v", err) 347 | } 348 | if _, err := el.Verify(pcr5); err != nil { 349 | t.Errorf("Verify() failed: %v", err) 350 | } 351 | } 352 | 353 | func TestAppendEvents(t *testing.T) { 354 | base, err := os.ReadFile("../testdata/legacydata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog") 355 | if err != nil { 356 | t.Fatalf("reading test data: %v", err) 357 | } 358 | 359 | extraLog, err := base64.StdEncoding.DecodeString(`AAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUAAABTcGVjIElEIEV2ZW50MDMAAAAAAAACAAEC 360 | AAAABAAUAAsAIAAACAAAAAYAAAACAAAABACX3UqVWDMNeg2Hkxyy6Q35wO4yBwsAVXbW4fKD8+xm 361 | Kv75L4ecBpvSR4d6bz+A7z1prUcKPuMrAQAACAISpgJpbWFfaGFzaD1zaGEyNTYgYXBwYXJtb3I9 362 | MSBwY2k9bm9hZXIsbm9hdHMgcHJpbnRrLmRldmttc2c9b24gc2xhYl9ub21lcmdlIGNvbnNvbGU9 363 | dHR5UzAsMTE1MjAwbjggY29uc29sZT10dHkwIGdsaW51eC1ib290LWltYWdlPTIwMjExMDI3LjAy 364 | LjAzIHF1aWV0IHNwbGFzaCBwbHltb3V0aC5pZ25vcmUtc2VyaWFsLWNvbnNvbGVzIGxzbT1sb2Nr 365 | ZG93bix5YW1hLGxvYWRwaW4sc2FmZXNldGlkLGludGVncml0eSxhcHBhcm1vcixzZWxpbnV4LHNt 366 | YWNrLHRvbW95byxicGYgcGFuaWM9MzAgaTkxNS5lbmFibGVfcHNyPTA=`) 367 | if err != nil { 368 | t.Fatal(err) 369 | } 370 | 371 | combined, err := AppendEvents(base, extraLog) 372 | if err != nil { 373 | t.Fatalf("CombineEventLogs() failed: %v", err) 374 | } 375 | 376 | // Make sure the combined log parses successfully and has one more 377 | // event than the base log. 378 | parsedBase, err := ParseEventLog(base, ParseOpts{}) 379 | if err != nil { 380 | t.Fatal(err) 381 | } 382 | parsed, err := ParseEventLog(combined, ParseOpts{}) 383 | if err != nil { 384 | t.Fatalf("ParseEventLog(combined_log) failed: %v", err) 385 | } 386 | 387 | if got, want := len(parsed.rawEvents), len(parsedBase.rawEvents)+1; got != want { 388 | t.Errorf("unexpected number of events in combined log: got %d, want %d", got, want) 389 | for i, e := range parsed.rawEvents { 390 | t.Logf("logs[%d] = %+v", i, e) 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /extract/secureboot.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "bytes" 19 | "crypto/x509" 20 | "errors" 21 | "fmt" 22 | 23 | "github.com/google/go-eventlog/tcg" 24 | ) 25 | 26 | // SecurebootState describes the secure boot status of a machine, as determined 27 | // by processing its event log. 28 | type SecurebootState struct { 29 | Enabled bool 30 | 31 | // PlatformKeys enumerates keys which can sign a key exchange key. 32 | PlatformKeys []x509.Certificate 33 | // PlatformKeys enumerates key hashes which can sign a key exchange key. 34 | PlatformKeyHashes [][]byte 35 | 36 | // ExchangeKeys enumerates keys which can sign a database of permitted or 37 | // forbidden keys. 38 | ExchangeKeys []x509.Certificate 39 | // ExchangeKeyHashes enumerates key hashes which can sign a database or 40 | // permitted or forbidden keys. 41 | ExchangeKeyHashes [][]byte 42 | 43 | // PermittedKeys enumerates keys which may sign binaries to run. 44 | PermittedKeys []x509.Certificate 45 | // PermittedHashes enumerates hashes which permit binaries to run. 46 | PermittedHashes [][]byte 47 | 48 | // ForbiddenKeys enumerates keys which must not permit a binary to run. 49 | ForbiddenKeys []x509.Certificate 50 | // ForbiddenKeys enumerates hashes which must not permit a binary to run. 51 | ForbiddenHashes [][]byte 52 | 53 | // PreSeparatorAuthority describes the use of a secure-boot key to authorize 54 | // the execution of a binary before the separator. 55 | PreSeparatorAuthority []x509.Certificate 56 | // PostSeparatorAuthority describes the use of a secure-boot key to authorize 57 | // the execution of a binary after the separator. 58 | PostSeparatorAuthority []x509.Certificate 59 | 60 | // DriverLoadSourceHints describes the origin of boot services drivers. 61 | // This data is not tamper-proof and must only be used as a hint. 62 | DriverLoadSourceHints []DriverLoadSource 63 | 64 | // DMAProtectionDisabled is true if the platform reports during boot that 65 | // DMA protection is supported but disabled. 66 | // 67 | // See: https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-kernel-dma-protection 68 | DMAProtectionDisabled bool 69 | } 70 | 71 | // DriverLoadSource describes the logical origin of a boot services driver. 72 | type DriverLoadSource uint8 73 | 74 | // Known sources for loaded drivers. 75 | const ( 76 | UnknownSource DriverLoadSource = iota 77 | PciMmioSource 78 | ) 79 | 80 | // ParseSecurebootStateLegacy parses a series of events to determine the 81 | // configuration of secure boot on a device. An error is returned if 82 | // the state cannot be determined, or if the event log is structured 83 | // in such a way that it may have been tampered post-execution of 84 | // platform firmware. 85 | // ParseSecurebootStateLegacy assumes events are sourced from a TPM event 86 | // log. It is meant for use with go-attestation. 87 | func ParseSecurebootStateLegacy(events []tcg.Event) (*SecurebootState, error) { 88 | // This algorithm verifies the following: 89 | // - All events in PCR 7 have event types which are expected in PCR 7. 90 | // - All events are parsable according to their event type. 91 | // - All events have digests values corresponding to their data/event type. 92 | // - No unverifiable events were present. 93 | // - All variables are specified before the separator and never duplicated. 94 | // - The SecureBoot variable has a value of 0 or 1. 95 | // - If SecureBoot was 1 (enabled), authority events were present indicating 96 | // keys were used to perform verification. 97 | // - If SecureBoot was 1 (enabled), platform + exchange + database keys 98 | // were specified. 99 | // - No UEFI debugger was attached. 100 | return ParseSecurebootState(events, TPMRegisterConfig, Opts{}) 101 | } 102 | 103 | // ParseSecurebootState parses a series of events to determine the 104 | // configuration of secure boot on a device. An error is returned if 105 | // the state cannot be determined, or if the event log is structured 106 | // in such a way that it may have been tampered post-execution of 107 | // platform firmware. 108 | func ParseSecurebootState(events []tcg.Event, registerCfg registerConfig, opts Opts) (*SecurebootState, error) { 109 | var ( 110 | out SecurebootState 111 | seenSeparator7 bool 112 | seenSeparator2 bool 113 | seenAuthority bool 114 | seenVars = map[string]bool{} 115 | driverSources [][]tcg.EFIDevicePathElement 116 | ) 117 | 118 | for _, e := range events { 119 | if e.MRIndex() != registerCfg.SecureBootIdx && e.MRIndex() != registerCfg.FirmwareDriverIdx { 120 | continue 121 | } 122 | 123 | et, err := tcg.UntrustedParseEventType(uint32(e.UntrustedType())) 124 | if err != nil { 125 | return nil, fmt.Errorf("unrecognised event type: %v", err) 126 | } 127 | digestVerify := DigestEquals(e, e.RawData()) 128 | 129 | switch e.MRIndex() { 130 | case registerCfg.SecureBootIdx: 131 | switch et { 132 | case tcg.Separator: 133 | if seenSeparator7 { 134 | return nil, fmt.Errorf("duplicate separator at event %d", e.Num()) 135 | } 136 | seenSeparator7 = true 137 | if !bytes.Equal(e.RawData(), []byte{0, 0, 0, 0}) { 138 | return nil, fmt.Errorf("invalid separator data at event %d: %v", e.Num(), e.RawData()) 139 | } 140 | if digestVerify != nil { 141 | return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.Num(), digestVerify) 142 | } 143 | 144 | case tcg.EFIAction: 145 | switch string(e.RawData()) { 146 | case "UEFI Debug Mode": 147 | return nil, errors.New("a UEFI debugger was present during boot") 148 | case "DMA Protection Disabled": 149 | if digestVerify != nil { 150 | return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.Num(), digestVerify) 151 | } 152 | out.DMAProtectionDisabled = true 153 | default: 154 | return nil, fmt.Errorf("event %d: unexpected EFI action event", e.Num()) 155 | } 156 | 157 | case tcg.EFIVariableDriverConfig: 158 | v, err := tcg.ParseUEFIVariableData(bytes.NewReader(e.RawData())) 159 | if err != nil { 160 | return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.Num(), err) 161 | } 162 | if _, seenBefore := seenVars[v.VarName()]; seenBefore { 163 | return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.Num()) 164 | } 165 | seenVars[v.VarName()] = true 166 | if seenSeparator7 { 167 | return nil, fmt.Errorf("event %d: variable %q specified after separator", e.Num(), v.VarName()) 168 | } 169 | 170 | if digestVerify != nil { 171 | return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.Num(), digestVerify) 172 | } 173 | 174 | switch v.VarName() { 175 | case "SecureBoot": 176 | if len(v.VariableData) == 1 { 177 | out.Enabled = v.VariableData[0] == 1 178 | } else if len(v.VariableData) == 0 && opts.AllowEmptySBVar { 179 | out.Enabled = false 180 | } else { 181 | return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.Num(), len(v.VariableData)) 182 | } 183 | 184 | case "PK": 185 | if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil { 186 | return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.Num(), err) 187 | } 188 | case "KEK": 189 | if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil { 190 | return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.Num(), err) 191 | } 192 | case "db": 193 | if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil { 194 | return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.Num(), err) 195 | } 196 | case "dbx": 197 | if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil { 198 | return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.Num(), err) 199 | } 200 | } 201 | 202 | case tcg.EFIVariableAuthority: 203 | v, err := tcg.ParseUEFIVariableData(bytes.NewReader(e.RawData())) 204 | if err != nil { 205 | return nil, fmt.Errorf("failed parsing UEFI variable data: %v", err) 206 | } 207 | 208 | a, err := tcg.ParseUEFIVariableAuthority(v) 209 | if err != nil { 210 | // Workaround for: https://github.com/google/go-attestation/issues/157 211 | if err == tcg.ErrSigMissingGUID { 212 | // Versions of shim which do not carry 213 | // https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50 214 | // have an erroneous additional byte in the event, which breaks digest 215 | // verification. If verification failed, we try removing the last byte. 216 | if digestVerify != nil && len(e.RawData()) > 0 { 217 | digestVerify = DigestEquals(e, e.RawData()[:len(e.RawData())-1]) 218 | } 219 | } else { 220 | return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.Num(), err) 221 | } 222 | } 223 | seenAuthority = true 224 | if digestVerify != nil { 225 | return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.Num(), digestVerify) 226 | } 227 | if !seenSeparator7 { 228 | out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...) 229 | } else { 230 | out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...) 231 | } 232 | 233 | default: 234 | if _, ok := registerCfg.AdditionalSecureBootIdxEvents[et]; ok { 235 | continue 236 | } 237 | return nil, fmt.Errorf("unexpected event type in MR%d: %v", e.MRIndex(), et) 238 | } 239 | 240 | case registerCfg.FirmwareDriverIdx: 241 | switch et { 242 | case tcg.Separator: 243 | if seenSeparator2 { 244 | return nil, fmt.Errorf("duplicate separator at event %d", e.Num()) 245 | } 246 | seenSeparator2 = true 247 | if !bytes.Equal(e.RawData(), []byte{0, 0, 0, 0}) { 248 | return nil, fmt.Errorf("invalid separator data at event %d: %v", e.Num(), e.RawData()) 249 | } 250 | if digestVerify != nil { 251 | return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.Num(), digestVerify) 252 | } 253 | 254 | case tcg.EFIBootServicesDriver: 255 | if !seenSeparator2 { 256 | imgLoad, err := tcg.ParseEFIImageLoad(bytes.NewReader(e.RawData())) 257 | if err != nil { 258 | return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.Num(), err) 259 | } 260 | dp, err := imgLoad.DevicePath() 261 | if err != nil { 262 | return nil, fmt.Errorf("failed to parse device path for driver load event %d: %v", e.Num(), err) 263 | } 264 | driverSources = append(driverSources, dp) 265 | } 266 | } 267 | } 268 | } 269 | 270 | // Compute driver source hints based on the EFI device path observed in 271 | // EFI Boot-services driver-load events. 272 | sourceLoop: 273 | for _, source := range driverSources { 274 | // We consider a driver to have originated from PCI-MMIO if any number 275 | // of elements in the device path [1] were PCI devices, and are followed by 276 | // an element representing a "relative offset range" read. 277 | // In the wild, we have typically observed 4-tuple device paths for such 278 | // devices: ACPI device -> PCI device -> PCI device -> relative offset. 279 | // 280 | // [1]: See section 9 of the UEFI specification v2.6 or greater. 281 | var seenPCI bool 282 | for _, e := range source { 283 | // subtype 0x1 corresponds to a PCI device (See: 9.3.2.1) 284 | if e.Type == tcg.HardwareDevice && e.Subtype == 0x1 { 285 | seenPCI = true 286 | } 287 | // subtype 0x8 corresponds to "relative offset range" (See: 9.3.6.8) 288 | if seenPCI && e.Type == tcg.MediaDevice && e.Subtype == 0x8 { 289 | out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, PciMmioSource) 290 | continue sourceLoop 291 | } 292 | } 293 | out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, UnknownSource) 294 | } 295 | 296 | if !out.Enabled { 297 | return &out, nil 298 | } 299 | 300 | if !seenAuthority { 301 | return nil, errors.New("secure boot was enabled but no key was used") 302 | } 303 | if len(out.PlatformKeys) == 0 && len(out.PlatformKeyHashes) == 0 { 304 | return nil, errors.New("secure boot was enabled but no platform keys were known") 305 | } 306 | if len(out.ExchangeKeys) == 0 && len(out.ExchangeKeyHashes) == 0 { 307 | return nil, errors.New("secure boot was enabled but no key exchange keys were known") 308 | } 309 | if len(out.PermittedKeys) == 0 && len(out.PermittedHashes) == 0 { 310 | return nil, errors.New("secure boot was enabled but no keys or hashes were permitted") 311 | } 312 | return &out, nil 313 | } 314 | -------------------------------------------------------------------------------- /cel/canonical_eventlog.go: -------------------------------------------------------------------------------- 1 | // Package cel contains some basic operations of Canonical Eventlog. 2 | // Based on Canonical EventLog Spec (Draft) Version: TCG_IWG_CEL_v1_r0p37. 3 | package cel 4 | 5 | import ( 6 | "bytes" 7 | "crypto" 8 | "encoding/binary" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/google/go-eventlog/register" 13 | "github.com/google/go-tpm/legacy/tpm2" 14 | ) 15 | 16 | // TopLevelEventType represents the CEL spec's known CELR data types for TPMS_CEL_EVENT. 17 | type TopLevelEventType uint8 18 | 19 | // MRType represents the type of measurement register used in the CEL for field 20 | // CEL_PCR_NVindex TLV. 21 | type MRType TopLevelEventType 22 | 23 | const ( 24 | // CEL spec 5.1 25 | recnumTypeValue TopLevelEventType = 0 26 | 27 | // PCRType indicates a PCR event index 28 | PCRType MRType = 1 29 | // NV Indexes are unsupported. 30 | _ MRType = 2 31 | // CCMRType indicates a RTMR event index 32 | CCMRType MRType = 108 33 | 34 | digestsTypeValue TopLevelEventType = 3 35 | 36 | tlvTypeFieldLength int = 1 37 | tlvLengthFieldLength int = 4 38 | 39 | recnumValueLength uint32 = 8 // support up to 2^64 records 40 | regIndexValueLength uint32 = 1 // support up to 256 registers 41 | ) 42 | 43 | // MRExtender extends an implementation-specific measurement register at the 44 | // specified bank and index with the supplied digest. 45 | type MRExtender func(crypto.Hash, int, []byte) error 46 | 47 | // TLV definition according to CEL spec TCG_IWG_CEL_v1_r0p37, page 16. 48 | // Length is implicitly defined by len(Value), using uint32 big-endian 49 | // when encoding. 50 | type TLV struct { 51 | Type uint8 52 | Value []byte 53 | } 54 | 55 | // MarshalBinary marshals a TLV to a byte slice. 56 | func (t TLV) MarshalBinary() (data []byte, err error) { 57 | buf := make([]byte, len(t.Value)+tlvTypeFieldLength+tlvLengthFieldLength) 58 | 59 | buf[0] = t.Type 60 | binary.BigEndian.PutUint32(buf[tlvTypeFieldLength:], uint32(len(t.Value))) 61 | copy(buf[tlvTypeFieldLength+tlvLengthFieldLength:], t.Value) 62 | 63 | return buf, nil 64 | } 65 | 66 | // UnmarshalBinary unmarshal a byte slice to a TLV. 67 | func (t *TLV) UnmarshalBinary(data []byte) error { 68 | valueLength := binary.BigEndian.Uint32(data[tlvTypeFieldLength : tlvTypeFieldLength+tlvLengthFieldLength]) 69 | 70 | if valueLength != uint32(len(data[tlvTypeFieldLength+tlvLengthFieldLength:])) { 71 | return fmt.Errorf("TLV Length doesn't match the size of its Value") 72 | } 73 | t.Type = data[0] 74 | t.Value = data[tlvTypeFieldLength+tlvLengthFieldLength:] 75 | 76 | return nil 77 | } 78 | 79 | // unmarshalFirstTLV reads and parse the first TLV from the bytes buffer. The function will 80 | // return io.EOF if the buf ends unexpectedly or cannot fill the TLV. 81 | func unmarshalFirstTLV(buf *bytes.Buffer) (tlv TLV, err error) { 82 | typeByte, err := buf.ReadByte() 83 | if err != nil { 84 | return tlv, err 85 | } 86 | var data []byte 87 | data = append(data, typeByte) 88 | 89 | // get the length 90 | lengthBytes := make([]byte, tlvLengthFieldLength) 91 | bytesRead, err := buf.Read(lengthBytes) 92 | if err != nil { 93 | return TLV{}, err 94 | } 95 | if bytesRead != tlvLengthFieldLength { 96 | return TLV{}, io.EOF 97 | } 98 | valueLength := binary.BigEndian.Uint32(lengthBytes) 99 | data = append(data, lengthBytes...) 100 | 101 | valueBytes := make([]byte, valueLength) 102 | bytesRead, err = buf.Read(valueBytes) 103 | if err != nil { 104 | return TLV{}, err 105 | } 106 | if uint32(bytesRead) != valueLength { 107 | return TLV{}, io.EOF 108 | } 109 | data = append(data, valueBytes...) 110 | 111 | if err = (&tlv).UnmarshalBinary(data); err != nil { 112 | return TLV{}, err 113 | } 114 | return tlv, nil 115 | } 116 | 117 | // Record represents a Canonical Eventlog Record. 118 | type Record struct { 119 | RecNum uint64 120 | // Generic Measurement Register index number, register type 121 | // is determined by IndexType 122 | Index uint8 123 | IndexType MRType 124 | Digests map[crypto.Hash][]byte 125 | Content TLV 126 | } 127 | 128 | // Content is a interface for the content in CELR. 129 | type Content interface { 130 | GenerateDigest(crypto.Hash) ([]byte, error) 131 | TLV() (TLV, error) 132 | } 133 | 134 | // CEL represents a Canonical Event Log, which contains a list of Records. 135 | type CEL interface { 136 | // Records returns all the records in the CEL. 137 | Records() []Record 138 | // AppendEvent appends a new record to the CEL. 139 | AppendEvent(Content, []crypto.Hash, int, MRExtender) error 140 | // EncodeCEL returns the TLV encoding of the CEL. 141 | EncodeCEL(*bytes.Buffer) error 142 | // Replay verifies the contents of the event log with the given MR bank. 143 | Replay(register.MRBank) error 144 | // MRType returns the measurement register type used in the CEL. 145 | MRType() MRType 146 | } 147 | 148 | // eventLog represents a Canonical Event Log, which contains a list of Records. 149 | type eventLog struct { 150 | Recs []Record 151 | Type MRType 152 | } 153 | 154 | // NewPCR returns a CEL with events measured in TPM PCRs. 155 | func NewPCR() CEL { 156 | return &eventLog{Type: PCRType} 157 | } 158 | 159 | // NewConfComputeMR returns a CEL with events measured in confidential 160 | // computing measurement registers. 161 | func NewConfComputeMR() CEL { 162 | return &eventLog{Type: CCMRType} 163 | } 164 | 165 | // generateDigestMap computes hashes with the given hash algos and the given event 166 | func generateDigestMap(hashAlgos []crypto.Hash, event Content) (map[crypto.Hash][]byte, error) { 167 | digestsMap := make(map[crypto.Hash][]byte) 168 | for _, hashAlgo := range hashAlgos { 169 | digest, err := event.GenerateDigest(hashAlgo) 170 | if err != nil { 171 | return digestsMap, err 172 | } 173 | digestsMap[hashAlgo] = digest 174 | } 175 | return digestsMap, nil 176 | } 177 | 178 | // AppendEvent appends a new MR record to the CEL. 179 | func (c *eventLog) AppendEvent(event Content, bankAlgos []crypto.Hash, mrIndex int, extender MRExtender) error { 180 | if len(bankAlgos) == 0 || mrIndex < 0 { 181 | return fmt.Errorf("failed to append event with banks %v, measurement register index %v", bankAlgos, mrIndex) 182 | } 183 | if err := supportedMRType(c.Type); err != nil { 184 | return err 185 | } 186 | 187 | digestMap, err := generateDigestMap(bankAlgos, event) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | for bank, dgst := range digestMap { 193 | if err := extender(bank, mrIndex, dgst); err != nil { 194 | return fmt.Errorf("failed to extend event to MR%d on bank %v: %v", mrIndex, bank, err) 195 | } 196 | } 197 | 198 | eventTlv, err := event.TLV() 199 | if err != nil { 200 | return err 201 | } 202 | 203 | celrPCR := Record{ 204 | RecNum: uint64(len(c.Recs)), 205 | Index: uint8(mrIndex), 206 | Digests: digestMap, 207 | Content: eventTlv, 208 | IndexType: c.Type, 209 | } 210 | 211 | c.Recs = append(c.Recs, celrPCR) 212 | return nil 213 | } 214 | 215 | func supportedMRType(mrType MRType) error { 216 | if mrType != PCRType && mrType != CCMRType { 217 | return fmt.Errorf("received unknown type of measurement register: %d", mrType) 218 | } 219 | return nil 220 | } 221 | 222 | func createRecNumField(recNum uint64) TLV { 223 | value := make([]byte, recnumValueLength) 224 | binary.BigEndian.PutUint64(value, recNum) 225 | return TLV{uint8(recnumTypeValue), value} 226 | } 227 | 228 | // UnmarshalRecNum takes in a TLV with its type equals to the recnum type value (0), and 229 | // return its record number. 230 | func unmarshalRecNum(tlv TLV) (uint64, error) { 231 | if tlv.Type != uint8(recnumTypeValue) { 232 | return 0, fmt.Errorf("type of the TLV [%d] indicates it is not a recnum field [%d]", 233 | tlv.Type, recnumTypeValue) 234 | } 235 | if uint32(len(tlv.Value)) != recnumValueLength { 236 | return 0, fmt.Errorf( 237 | "length of the value of the TLV [%d] doesn't match the defined length [%d] of value for recnum", 238 | len(tlv.Value), recnumValueLength) 239 | } 240 | return binary.BigEndian.Uint64(tlv.Value), nil 241 | } 242 | 243 | func createIndexField(indexType uint8, indexNum uint8) TLV { 244 | return TLV{indexType, []byte{indexNum}} 245 | } 246 | 247 | // unmarshalIndex takes in a TLV with its type equals to the PCR or CCMR type value, and 248 | // return its index number. 249 | func unmarshalIndex(tlv TLV) (indexType MRType, index uint8, err error) { 250 | switch tlv.Type { 251 | case uint8(PCRType): 252 | indexType = PCRType 253 | case uint8(CCMRType): 254 | indexType = CCMRType 255 | default: 256 | return 0, 0, fmt.Errorf("type of the TLV [%d] indicates it is not a PCR [%d] or a CCMR [%d] field ", 257 | tlv.Type, uint8(PCRType), uint8(CCMRType)) 258 | } 259 | if uint32(len(tlv.Value)) != regIndexValueLength { 260 | return 0, 0, fmt.Errorf( 261 | "length of the value of the TLV [%d] doesn't match the defined length [%d] of value for a register index field", 262 | len(tlv.Value), regIndexValueLength) 263 | } 264 | 265 | return indexType, tlv.Value[0], nil 266 | } 267 | 268 | func createDigestField(digestMap map[crypto.Hash][]byte) (TLV, error) { 269 | var buf bytes.Buffer 270 | for hashAlgo, hash := range digestMap { 271 | if len(hash) != hashAlgo.Size() { 272 | return TLV{}, fmt.Errorf("digest length [%d] doesn't match the expected length [%d] for the hash algorithm", 273 | len(hash), hashAlgo.Size()) 274 | } 275 | tpmHashAlg, err := tpm2.HashToAlgorithm(hashAlgo) 276 | if err != nil { 277 | return TLV{}, err 278 | } 279 | singleDigestTLV := TLV{uint8(tpmHashAlg), hash} 280 | d, err := singleDigestTLV.MarshalBinary() 281 | if err != nil { 282 | return TLV{}, err 283 | } 284 | _, err = buf.Write(d) 285 | if err != nil { 286 | return TLV{}, err 287 | } 288 | } 289 | return TLV{uint8(digestsTypeValue), buf.Bytes()}, nil 290 | } 291 | 292 | // UnmarshalDigests takes in a TLV with its type equals to the digests type value (3), and 293 | // return its digests content in a map, the key is its TPM hash algorithm. 294 | func unmarshalDigests(tlv TLV) (digestsMap map[crypto.Hash][]byte, err error) { 295 | if tlv.Type != uint8(digestsTypeValue) { 296 | return nil, fmt.Errorf("type of the TLV indicates it doesn't contain digests") 297 | } 298 | 299 | buf := bytes.NewBuffer(tlv.Value) 300 | digestsMap = make(map[crypto.Hash][]byte) 301 | 302 | for buf.Len() > 0 { 303 | digestTLV, err := unmarshalFirstTLV(buf) 304 | if err == io.EOF { 305 | return nil, fmt.Errorf("buffer ends unexpectedly") 306 | } else if err != nil { 307 | return nil, err 308 | } 309 | hashAlg, err := tpm2.Algorithm(digestTLV.Type).Hash() 310 | if err != nil { 311 | return nil, err 312 | } 313 | digestsMap[hashAlg] = digestTLV.Value 314 | } 315 | return digestsMap, nil 316 | } 317 | 318 | // EncodeCELR encodes the CELR to bytes according to the CEL spec and write them 319 | // to the bytes byffer. 320 | func (r *Record) EncodeCELR(buf *bytes.Buffer) error { 321 | recnumField, err := createRecNumField(r.RecNum).MarshalBinary() 322 | if err != nil { 323 | return err 324 | } 325 | 326 | indexField, err := createIndexField(uint8(r.IndexType), r.Index).MarshalBinary() 327 | if err != nil { 328 | return err 329 | } 330 | digests, err := createDigestField(r.Digests) 331 | if err != nil { 332 | return err 333 | } 334 | digestsField, err := digests.MarshalBinary() 335 | if err != nil { 336 | return err 337 | } 338 | eventField, err := r.Content.MarshalBinary() 339 | if err != nil { 340 | return err 341 | } 342 | _, err = buf.Write(recnumField) 343 | if err != nil { 344 | return err 345 | } 346 | _, err = buf.Write(indexField) 347 | if err != nil { 348 | return err 349 | } 350 | _, err = buf.Write(digestsField) 351 | if err != nil { 352 | return err 353 | } 354 | _, err = buf.Write(eventField) 355 | if err != nil { 356 | return err 357 | } 358 | return nil 359 | } 360 | 361 | // EncodeCEL encodes the CEL to bytes according to the CEL spec and write them 362 | // to the bytes buffer. 363 | func (c *eventLog) EncodeCEL(buf *bytes.Buffer) error { 364 | for _, record := range c.Recs { 365 | if err := record.EncodeCELR(buf); err != nil { 366 | return err 367 | } 368 | } 369 | return nil 370 | } 371 | 372 | // DecodeToCEL will read the buf for CEL, will return err if the buffer 373 | // is not complete. 374 | func DecodeToCEL(buf *bytes.Buffer) (CEL, error) { 375 | var cel eventLog 376 | for buf.Len() > 0 { 377 | celr, err := decodeToCELR(buf) 378 | if err == io.EOF { 379 | return &eventLog{}, fmt.Errorf("buffer ends unexpectedly") 380 | } 381 | if err != nil { 382 | return &eventLog{}, err 383 | } 384 | cel.Recs = append(cel.Recs, celr) 385 | } 386 | if len(cel.Recs) > 1 { 387 | zeroMRType := MRType(cel.Recs[0].IndexType) 388 | for _, rec := range cel.Recs { 389 | mrType := MRType(rec.IndexType) 390 | if err := supportedMRType(mrType); err != nil { 391 | return &eventLog{}, fmt.Errorf("bad record %v: %v", rec.RecNum, err) 392 | } 393 | if mrType != zeroMRType { 394 | return &eventLog{}, fmt.Errorf("bad record %v: found differing MR types in the CEL: got %v, expected %v", rec.RecNum, mrType, zeroMRType) 395 | } 396 | } 397 | cel.Type = zeroMRType 398 | } 399 | return &cel, nil 400 | } 401 | 402 | // decodeToCELR will read the buf for the next CELR, will return err if 403 | // failed to unmarshal a correct CELR TLV from the buffer. 404 | func decodeToCELR(buf *bytes.Buffer) (r Record, err error) { 405 | recnum, err := unmarshalFirstTLV(buf) 406 | if err != nil { 407 | return Record{}, err 408 | } 409 | r.RecNum, err = unmarshalRecNum(recnum) 410 | if err != nil { 411 | return Record{}, err 412 | } 413 | 414 | regIndex, err := unmarshalFirstTLV(buf) 415 | if err != nil { 416 | return Record{}, err 417 | } 418 | r.IndexType, r.Index, err = unmarshalIndex(regIndex) 419 | if err != nil { 420 | return Record{}, err 421 | } 422 | 423 | digests, err := unmarshalFirstTLV(buf) 424 | if err != nil { 425 | return Record{}, err 426 | } 427 | r.Digests, err = unmarshalDigests(digests) 428 | if err != nil { 429 | return Record{}, err 430 | } 431 | 432 | r.Content, err = unmarshalFirstTLV(buf) 433 | if err != nil { 434 | return Record{}, err 435 | } 436 | return r, nil 437 | } 438 | 439 | // Replay takes the digests from a Canonical Event Log and carries out the 440 | // extend sequence for each register (PCR, RTMR) in the log. It then compares 441 | // the final digests against a bank of register values to see if they match. 442 | // make sure CEL has only one indexType event 443 | func (c *eventLog) Replay(regs register.MRBank) error { 444 | cryptoHash, err := regs.CryptoHash() 445 | if err != nil { 446 | return err 447 | } 448 | replayed := make(map[uint8][]byte) 449 | for _, record := range c.Recs { 450 | if _, ok := replayed[record.Index]; !ok { 451 | replayed[record.Index] = make([]byte, cryptoHash.Size()) 452 | } 453 | hasher := cryptoHash.New() 454 | digestsMap := record.Digests 455 | digest, ok := digestsMap[cryptoHash] 456 | if !ok { 457 | return fmt.Errorf("the CEL record did not contain a %v digest", cryptoHash) 458 | } 459 | hasher.Write(replayed[record.Index]) 460 | hasher.Write(digest) 461 | replayed[record.Index] = hasher.Sum(nil) 462 | } 463 | 464 | // to a map for easy matching 465 | registers := make(map[int][]byte) 466 | for _, r := range regs.MRs() { 467 | registers[r.Idx()] = r.Dgst() 468 | } 469 | 470 | var failedReplayRegs []uint8 471 | for replayReg, replayDigest := range replayed { 472 | bankDigest, ok := registers[int(replayReg)] 473 | if !ok { 474 | return fmt.Errorf("the CEL contains record(s) for register %d without a matching register in the given bank to verify", replayReg) 475 | } 476 | if !bytes.Equal(bankDigest, replayDigest) { 477 | failedReplayRegs = append(failedReplayRegs, replayReg) 478 | } 479 | } 480 | 481 | if len(failedReplayRegs) == 0 { 482 | return nil 483 | } 484 | 485 | return fmt.Errorf("CEL replay failed for these registers in bank %v: %v", cryptoHash, failedReplayRegs) 486 | } 487 | 488 | func (c *eventLog) Records() []Record { 489 | return c.Recs 490 | } 491 | 492 | func (c *eventLog) MRType() MRType { 493 | return c.Type 494 | } 495 | 496 | // VerifyDigests checks the digest generated by the given record's content to make sure they are equal to 497 | // the digests in the digestMap. 498 | func VerifyDigests(c Content, digestMap map[crypto.Hash][]byte) error { 499 | for hash, digest := range digestMap { 500 | generatedDigest, err := c.GenerateDigest(hash) 501 | if err != nil { 502 | return err 503 | } 504 | if !bytes.Equal(generatedDigest, digest) { 505 | return fmt.Errorf("CEL record content digest verification failed for %s", hash) 506 | } 507 | } 508 | return nil 509 | } 510 | -------------------------------------------------------------------------------- /extract/extract_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract 16 | 17 | import ( 18 | "bytes" 19 | "crypto" 20 | "crypto/rand" 21 | "encoding/hex" 22 | "math/big" 23 | "os" 24 | "strings" 25 | "testing" 26 | 27 | "github.com/google/go-eventlog/internal/testutil" 28 | "github.com/google/go-eventlog/register" 29 | "github.com/google/go-eventlog/tcg" 30 | "github.com/google/go-eventlog/testdata" 31 | "google.golang.org/protobuf/proto" 32 | 33 | pb "github.com/google/go-eventlog/proto/state" 34 | ) 35 | 36 | func TestExtractFirmwareLogStateRTMR(t *testing.T) { 37 | tests := []struct { 38 | name string 39 | mutate func([]tcg.Event) 40 | expectErr bool 41 | }{ 42 | { 43 | name: "Happy Path", 44 | mutate: func(_ []tcg.Event) {}, 45 | }, 46 | { 47 | name: "Nil Digests", 48 | mutate: func(evts []tcg.Event) { 49 | for i := range evts { 50 | evts[i].Digest = nil 51 | } 52 | }, 53 | expectErr: true, 54 | }, 55 | { 56 | name: "Bad Digests", 57 | mutate: func(evts []tcg.Event) { 58 | for i := range evts { 59 | b := make([]byte, len(evts[i].Digest)) 60 | if _, err := rand.Read(b); err != nil { 61 | t.Fatal(err) 62 | } 63 | evts[i].Digest = b 64 | } 65 | }, 66 | expectErr: true, 67 | }, 68 | { 69 | name: "Nil Data", 70 | mutate: func(evts []tcg.Event) { 71 | for i := range evts { 72 | evts[i].Data = nil 73 | } 74 | }, 75 | expectErr: true, 76 | }, 77 | { 78 | name: "Bad Data", 79 | mutate: func(evts []tcg.Event) { 80 | for i := range evts { 81 | b := make([]byte, len(evts[i].Data)) 82 | if _, err := rand.Read(b); err != nil { 83 | t.Fatal(err) 84 | } 85 | evts[i].Data = b 86 | } 87 | }, 88 | expectErr: true, 89 | }, 90 | { 91 | name: "Zero Index", 92 | mutate: func(evts []tcg.Event) { 93 | for i := range evts { 94 | evts[i].Index = 0 95 | } 96 | }, 97 | expectErr: true, 98 | }, 99 | { 100 | name: "Rand Index", 101 | mutate: func(evts []tcg.Event) { 102 | for i := range evts { 103 | bigInt, err := rand.Int(rand.Reader, big.NewInt(4)) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | evts[i].Index = int(bigInt.Int64()) 108 | } 109 | }, 110 | expectErr: true, 111 | }, 112 | { 113 | name: "Zero Type", 114 | mutate: func(evts []tcg.Event) { 115 | for i := range evts { 116 | evts[i].Type = 0 117 | } 118 | }, 119 | expectErr: true, 120 | }, 121 | { 122 | name: "More Separators", 123 | mutate: func(evts []tcg.Event) { 124 | for i := range evts { 125 | evts[i].Type = tcg.Separator 126 | } 127 | }, 128 | expectErr: true, 129 | }, 130 | { 131 | name: "More EFIAction", 132 | mutate: func(evts []tcg.Event) { 133 | for i := range evts { 134 | evts[i].Type = tcg.EFIAction 135 | } 136 | }, 137 | expectErr: true, 138 | }, 139 | { 140 | name: "More IPL", 141 | mutate: func(evts []tcg.Event) { 142 | for i := range evts { 143 | evts[i].Type = tcg.Ipl 144 | } 145 | }, 146 | expectErr: true, 147 | }, 148 | } 149 | for _, tc := range tests { 150 | t.Run(tc.name, func(t *testing.T) { 151 | evts := getCCELEvents(t) 152 | tc.mutate(evts) 153 | fs, err := FirmwareLogState(evts, crypto.SHA384, RTMRRegisterConfig, Opts{Loader: GRUB}) 154 | if (err != nil) != tc.expectErr { 155 | t.Errorf("FirmwareLogState(%v) = got %v, wantErr: %v", tc.name, err, tc.expectErr) 156 | } 157 | if fs.LogType != pb.LogType_LOG_TYPE_CC { 158 | t.Errorf("FirmwareLogState(%v) = got LogType %v, want LogType: %v", tc.name, fs.LogType, pb.LogType_LOG_TYPE_CC) 159 | } 160 | }) 161 | } 162 | } 163 | 164 | func TestExtractFirmwareLogStateRTMRNilEvents(t *testing.T) { 165 | _, err := FirmwareLogState(nil, crypto.SHA384, RTMRRegisterConfig, Opts{Loader: GRUB}) 166 | if err == nil || !strings.Contains(err.Error(), "no GRUB measurements found") { 167 | t.Errorf("ExtractFirmwareLogState(nil): got %v, expected error no GRUB measurements found", err) 168 | } 169 | } 170 | 171 | func getCCELEvents(t *testing.T) []tcg.Event { 172 | elBytes, err := os.ReadFile("../testdata/eventlogs/ccel/cos-113-intel-tdx.bin") 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | 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") 177 | 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") 178 | 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") 179 | mrs := []register.MR{ 180 | register.RTMR{Index: 0, Digest: rtmr0}, 181 | register.RTMR{Index: 1, Digest: rtmr1}, 182 | register.RTMR{Index: 2, Digest: rtmr2}, 183 | } 184 | events, err := tcg.ParseAndReplay(elBytes, mrs, tcg.ParseOpts{AllowPadding: true}) 185 | if err != nil { 186 | t.Fatal(err) 187 | } 188 | return events 189 | } 190 | 191 | func TestExtractFirmwareLogStateTPM(t *testing.T) { 192 | tests := []struct { 193 | name string 194 | mutate func([]tcg.Event) 195 | expectErr bool 196 | }{ 197 | { 198 | name: "Happy Path", 199 | mutate: func(_ []tcg.Event) {}, 200 | }, 201 | { 202 | name: "Nil Digests", 203 | mutate: func(evts []tcg.Event) { 204 | for i := range evts { 205 | evts[i].Digest = nil 206 | } 207 | }, 208 | expectErr: true, 209 | }, 210 | { 211 | name: "Bad Digests", 212 | mutate: func(evts []tcg.Event) { 213 | for i := range evts { 214 | b := make([]byte, len(evts[i].Digest)) 215 | if _, err := rand.Read(b); err != nil { 216 | t.Fatal(err) 217 | } 218 | evts[i].Digest = b 219 | } 220 | }, 221 | expectErr: true, 222 | }, 223 | { 224 | name: "Nil Data", 225 | mutate: func(evts []tcg.Event) { 226 | for i := range evts { 227 | evts[i].Data = nil 228 | } 229 | }, 230 | expectErr: true, 231 | }, 232 | { 233 | name: "Bad Data", 234 | mutate: func(evts []tcg.Event) { 235 | for i := range evts { 236 | b := make([]byte, len(evts[i].Data)) 237 | if _, err := rand.Read(b); err != nil { 238 | t.Fatal(err) 239 | } 240 | evts[i].Data = b 241 | } 242 | }, 243 | expectErr: true, 244 | }, 245 | { 246 | name: "Zero Index", 247 | mutate: func(evts []tcg.Event) { 248 | for i := range evts { 249 | evts[i].Index = 0 250 | } 251 | }, 252 | expectErr: true, 253 | }, 254 | { 255 | name: "Rand Index", 256 | mutate: func(evts []tcg.Event) { 257 | for i := range evts { 258 | bigInt, err := rand.Int(rand.Reader, big.NewInt(25)) 259 | if err != nil { 260 | t.Fatal(err) 261 | } 262 | evts[i].Index = int(bigInt.Int64()) 263 | } 264 | }, 265 | expectErr: true, 266 | }, 267 | { 268 | name: "Zero Type", 269 | mutate: func(evts []tcg.Event) { 270 | for i := range evts { 271 | evts[i].Type = 0 272 | } 273 | }, 274 | expectErr: true, 275 | }, 276 | { 277 | name: "More Separators", 278 | mutate: func(evts []tcg.Event) { 279 | for i := range evts { 280 | evts[i].Type = tcg.Separator 281 | } 282 | }, 283 | expectErr: true, 284 | }, 285 | { 286 | name: "More EFIAction", 287 | mutate: func(evts []tcg.Event) { 288 | for i := range evts { 289 | evts[i].Type = tcg.EFIAction 290 | } 291 | }, 292 | expectErr: true, 293 | }, 294 | { 295 | name: "More IPL", 296 | mutate: func(evts []tcg.Event) { 297 | for i := range evts { 298 | evts[i].Type = tcg.Ipl 299 | } 300 | }, 301 | expectErr: true, 302 | }, 303 | } 304 | for _, tc := range tests { 305 | t.Run(tc.name, func(t *testing.T) { 306 | hash, evts := getTPMELEvents(t) 307 | tc.mutate(evts) 308 | fs, err := FirmwareLogState(evts, hash, TPMRegisterConfig, Opts{Loader: GRUB}) 309 | if (err != nil) != tc.expectErr { 310 | t.Errorf("ExtractFirmwareLogState(%v) = got %v, wantErr: %v", tc.name, err, tc.expectErr) 311 | } 312 | if fs.LogType != pb.LogType_LOG_TYPE_TCG2 { 313 | t.Errorf("FirmwareLogState(%v) = got LogType %v, want LogType: %v", tc.name, fs.LogType, pb.LogType_LOG_TYPE_TCG2) 314 | } 315 | }) 316 | } 317 | } 318 | 319 | func TestExtractFirmwareLogStateNoLogType(t *testing.T) { 320 | hash, evts := getTPMELEvents(t) 321 | missingType := TPMRegisterConfig 322 | missingType.LogType = pb.LogType_LOG_TYPE_UNDEFINED 323 | fs, err := FirmwareLogState(evts, hash, missingType, Opts{Loader: GRUB}) 324 | if err != nil { 325 | t.Fatal("failed to extract FirmwareLogState") 326 | } 327 | if fs.LogType != pb.LogType_LOG_TYPE_UNDEFINED { 328 | t.Errorf("FirmwareLogState() = got LogType %v, want LogType: %v", fs.LogType, pb.LogType_LOG_TYPE_UNDEFINED) 329 | } 330 | } 331 | 332 | func TestExtractFirmwareLogStateTPMNilEvents(t *testing.T) { 333 | _, err := FirmwareLogState(nil, crypto.SHA384, TPMRegisterConfig, Opts{Loader: GRUB}) 334 | if err == nil || !strings.Contains(err.Error(), "no GRUB measurements found") { 335 | t.Errorf("ExtractFirmwareLogState(nil): got %v, expected error no GRUB measurements found", err) 336 | } 337 | } 338 | 339 | func TestGrubStateFromTPMLogWithModifiedNullTerminator(t *testing.T) { 340 | hash, tpmEvents := getTPMELEvents(t) 341 | 342 | // Make sure the original events can parse successfully. 343 | if _, err := GrubStateFromTPMLog(hash, tpmEvents); err != nil { 344 | t.Fatal(err) 345 | } 346 | 347 | // Change the null terminator 348 | for _, e := range tpmEvents { 349 | if e.Index == 8 { 350 | if e.Data[len(e.Data)-1] == '\x00' { 351 | e.Data[len(e.Data)-1] = '\xff' 352 | } 353 | } 354 | } 355 | 356 | if _, err := GrubStateFromTPMLog(hash, tpmEvents); err == nil { 357 | t.Error("GrubStateFromTPMLog should fail after modifying the null terminator") 358 | } 359 | } 360 | 361 | func TestGrubStateFromRTMRLogWithModifiedNullTerminator(t *testing.T) { 362 | ccelEvents := getCCELEvents(t) 363 | 364 | // Make sure the original events can parse successfully. 365 | if _, err := GrubStateFromRTMRLog(crypto.SHA384, ccelEvents); err != nil { 366 | t.Fatal(err) 367 | } 368 | 369 | for _, e := range ccelEvents { 370 | if e.Data[len(e.Data)-1] == '\x00' { 371 | e.Data[len(e.Data)-1] = '\xff' 372 | } 373 | } 374 | if _, err := GrubStateFromRTMRLog(crypto.SHA384, ccelEvents); err == nil { 375 | t.Error("GrubStateFromRTMRLog should fail after modifying the null terminator") 376 | } 377 | } 378 | 379 | func TestEfiState(t *testing.T) { 380 | tests := []struct { 381 | name string 382 | events func() (crypto.Hash, []tcg.Event) 383 | registserConfig registerConfig 384 | wantPass bool 385 | wantEfiState *pb.EfiState 386 | opts Opts 387 | }{ 388 | { 389 | name: "success with TPM logs", 390 | events: func() (crypto.Hash, []tcg.Event) { 391 | return getTPMELEvents(t) 392 | }, 393 | registserConfig: TPMRegisterConfig, 394 | wantPass: true, 395 | wantEfiState: &pb.EfiState{ 396 | Apps: []*pb.EfiApp{ 397 | { 398 | Digest: []byte("rM\xe6\x84M\xd0\xfea\x8b\xa5wl{\xca\x07(\xbe8\xa6TN$\xe4N\xf2Y\xb9\x87\xb7\xab΀"), 399 | }, 400 | { 401 | Digest: []byte("^\x8c\xb7Z\xcd\xf8\xe0\x9e_\xc1L\xc2\xd6\xce\x0c\"\x88\xaf \x89v\xd9s\t\x85\x1cf\x1e\x91\xec\x1e\x03"), 402 | }, 403 | }, 404 | }, 405 | opts: Opts{ 406 | AllowEFIAppBeforeCallingEvent: false, 407 | }, 408 | }, 409 | { 410 | name: "success with CCEL logs", 411 | events: func() (crypto.Hash, []tcg.Event) { 412 | return crypto.SHA384, getCCELEvents(t) 413 | }, 414 | registserConfig: RTMRRegisterConfig, 415 | wantPass: true, 416 | wantEfiState: &pb.EfiState{ 417 | Apps: []*pb.EfiApp{ 418 | { 419 | Digest: []byte("Z\x10\x02l\x9a\xd4\x1d\x1f\x90ܜ\xfe\x88\xbc\xab\xe1\x84,\xcf\xd8T\x95\xc8\x1b\x1a\x1a\xb9&\xa9\xef#\xb5\xd2\xe6\x0e\xef\xeb\xa0A[\xbe\\\x8c2\x8a\x89\x9a\n"), 420 | }, 421 | { 422 | Digest: []byte("\xb1\xfb\x7fL\x06\x89\xf5\xa9 \xb8\x00\xb2`pu\xf4\x90o\x8c\x82\x82\xd4NV\xfc\x99\x1e\xc0\x1f\x1a\xda\xc1v\xd2\x04\n&\xf1E=\xf1\x12\xd7\xc4\xf4)?\xc9"), 423 | }, 424 | }, 425 | }, 426 | opts: Opts{ 427 | AllowEFIAppBeforeCallingEvent: false, 428 | }, 429 | }, 430 | { 431 | name: "nil EFI state with missing ExitBootServicesInvocation event in TPM logs", 432 | events: func() (crypto.Hash, []tcg.Event) { 433 | hash, evts := getTPMELEvents(t) 434 | var failedEvts []tcg.Event 435 | for _, e := range evts { 436 | if bytes.Equal(e.RawData(), []byte(tcg.ExitBootServicesInvocation)) { 437 | continue 438 | } 439 | failedEvts = append(failedEvts, e) 440 | } 441 | return hash, failedEvts 442 | }, 443 | registserConfig: TPMRegisterConfig, 444 | wantPass: true, 445 | wantEfiState: nil, 446 | opts: Opts{ 447 | AllowEFIAppBeforeCallingEvent: false, 448 | }, 449 | }, 450 | { 451 | name: "failed with missing CallingEFIApp event in TPM logs", 452 | events: func() (crypto.Hash, []tcg.Event) { 453 | hash, evts := getTPMELEvents(t) 454 | var failedEvts []tcg.Event 455 | for _, e := range evts { 456 | if bytes.Equal(e.RawData(), []byte(tcg.CallingEFIApplication)) { 457 | continue 458 | } 459 | failedEvts = append(failedEvts, e) 460 | } 461 | return hash, failedEvts 462 | }, 463 | registserConfig: TPMRegisterConfig, 464 | wantPass: false, 465 | wantEfiState: nil, 466 | opts: Opts{ 467 | AllowEFIAppBeforeCallingEvent: false, 468 | }, 469 | }, 470 | { 471 | name: "failed with multiple separators in TPM logs", 472 | events: func() (crypto.Hash, []tcg.Event) { 473 | hash, evts := getTPMELEvents(t) 474 | for i := range evts { 475 | evts[i].Type = tcg.Separator 476 | } 477 | return hash, evts 478 | }, 479 | registserConfig: TPMRegisterConfig, 480 | wantPass: false, 481 | wantEfiState: nil, 482 | opts: Opts{ 483 | AllowEFIAppBeforeCallingEvent: false, 484 | }, 485 | }, 486 | { 487 | name: "failed with bad data in TPM logs", 488 | events: func() (crypto.Hash, []tcg.Event) { 489 | hash, evts := getTPMELEvents(t) 490 | for i := range evts { 491 | b := make([]byte, len(evts[i].Data)) 492 | if _, err := rand.Read(b); err != nil { 493 | t.Fatal(err) 494 | } 495 | evts[i].Data = b 496 | } 497 | return hash, evts 498 | }, 499 | registserConfig: TPMRegisterConfig, 500 | wantPass: false, 501 | wantEfiState: nil, 502 | opts: Opts{ 503 | AllowEFIAppBeforeCallingEvent: false, 504 | }, 505 | }, 506 | } 507 | for _, tc := range tests { 508 | t.Run(tc.name, func(t *testing.T) { 509 | hash, events := tc.events() 510 | efiState, err := EfiState(hash, events, tc.registserConfig, tc.opts) 511 | if gotPass := (err == nil); gotPass != tc.wantPass { 512 | t.Errorf("EfiState returned unexpected result, gotPass %v, but want %v", gotPass, tc.wantPass) 513 | } 514 | if !proto.Equal(efiState, tc.wantEfiState) { 515 | t.Errorf("EfiState returned unexpected state, got %+v, but want %+v", efiState, tc.wantEfiState) 516 | } 517 | }) 518 | } 519 | } 520 | 521 | func getTPMELEvents(t *testing.T) (crypto.Hash, []tcg.Event) { 522 | log := testdata.Ubuntu2404AmdSevSnpEventLog 523 | bank := testutil.MakePCRBank(pb.HashAlgo_SHA256, map[uint32][]byte{ 524 | 0: decodeHex("50597a27846e91d025eef597abbc89f72bff9af849094db97b0684d8bc4c515e"), 525 | 1: decodeHex("57344e1cc8c6619413df33013a7cd67915459f967395af41db21c1fa7ca9c307"), 526 | 2: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 527 | 3: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 528 | 4: decodeHex("abe8b3fa6aecb36c2fd93c6f6edde661c21b353d007410a2739d69bfa7e1b9be"), 529 | 5: decodeHex("0b0e1903aeb1bff649b82dba2cdcf5c4ffb75027e54f151ab00b3b989f16a300"), 530 | 6: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 531 | 7: decodeHex("33ad69850fb2c7f30b4f8b4bc10ed93fc954dc07fa726e84f50f3d192dc1c140"), 532 | 8: decodeHex("6932a3f71dc55ad3c1a6ac2196eeac26a1b7164b6bbfa106625d94088ec3ecc3"), 533 | 9: decodeHex("ce08798b283c7a0ddc5e9ad1d602304b945b741fc60c20e254eafa0f4782512b"), 534 | 14: decodeHex("306f9d8b94f17d93dc6e7cf8f5c79d652eb4c6c4d13de2dddc24af416e13ecaf"), 535 | }) 536 | cryptoHash, err := bank.CryptoHash() 537 | if err != nil { 538 | t.Fatal(err) 539 | } 540 | events, err := tcg.ParseAndReplay(log, bank.MRs(), tcg.ParseOpts{}) 541 | if err != nil { 542 | t.Fatal(err) 543 | 544 | } 545 | return cryptoHash, events 546 | } 547 | 548 | func decodeHex(hexStr string) []byte { 549 | bytes, err := hex.DecodeString(hexStr) 550 | if err != nil { 551 | panic(err) 552 | } 553 | return bytes 554 | } 555 | -------------------------------------------------------------------------------- /extract/secureboot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package extract_test 16 | 17 | import ( 18 | "bytes" 19 | "crypto" 20 | "crypto/sha256" 21 | "encoding/base64" 22 | "encoding/json" 23 | "os" 24 | "testing" 25 | 26 | "github.com/google/go-cmp/cmp" 27 | "github.com/google/go-eventlog/extract" 28 | "github.com/google/go-eventlog/internal/testutil" 29 | "github.com/google/go-eventlog/proto/state" 30 | "github.com/google/go-eventlog/register" 31 | "github.com/google/go-eventlog/tcg" 32 | ) 33 | 34 | func TestSecureBoot(t *testing.T) { 35 | data, err := os.ReadFile("../testdata/legacydata/windows_gcp_shielded_vm.json") 36 | if err != nil { 37 | t.Fatalf("reading test data: %v", err) 38 | } 39 | var dump testutil.Dump 40 | if err := json.Unmarshal(data, &dump); err != nil { 41 | t.Fatalf("parsing test data: %v", err) 42 | } 43 | 44 | el, err := tcg.ParseEventLog(dump.Log.Raw, tcg.ParseOpts{}) 45 | if err != nil { 46 | t.Fatalf("parsing event log: %v", err) 47 | } 48 | logBank := register.PCRBank{ 49 | TCGHashAlgo: state.HashAlgo(dump.Log.PCRAlg), 50 | PCRs: dump.Log.PCRs} 51 | events, err := el.Verify(logBank.MRs()) 52 | if err != nil { 53 | t.Fatalf("validating event log: %v", err) 54 | } 55 | 56 | sbState, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig, extract.Opts{}) 57 | if err != nil { 58 | t.Fatalf("ExtractSecurebootState() failed: %v", err) 59 | } 60 | 61 | if got, want := sbState.Enabled, true; got != want { 62 | t.Errorf("secureboot.Enabled = %v, want %v", got, want) 63 | } 64 | } 65 | 66 | // See: https://github.com/google/go-attestation/issues/157 67 | func TestSecureBootBug157(t *testing.T) { 68 | raw, err := os.ReadFile("../testdata/legacydata/sb_cert_eventlog") 69 | if err != nil { 70 | t.Fatalf("reading test data: %v", err) 71 | } 72 | elr, err := tcg.ParseEventLog(raw, tcg.ParseOpts{}) 73 | if err != nil { 74 | t.Fatalf("parsing event log: %v", err) 75 | } 76 | 77 | pcrs := []register.PCR{ 78 | {Index: '\x00', Digest: []byte("Q\xc3#\xde\f\fiOF\x01\xcd\xd0+\xebX\xff\x13b\x9ft"), DigestAlg: crypto.SHA1}, 79 | {Index: '\x01', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 80 | {Index: '\x02', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 81 | {Index: '\x03', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 82 | {Index: '\x04', Digest: []byte("\xb7q\x00\x8d\x17<\x02+\xc1oKM\x1a\u007f\x8b\x99\xed\x88\xee\xb1"), DigestAlg: crypto.SHA1}, 83 | {Index: '\x05', Digest: []byte("\xd79j\xc6\xe8\x87\xda\"ޠ;@\x95/p\xb8\xdbҩ\x96"), DigestAlg: crypto.SHA1}, 84 | {Index: '\x06', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 85 | {Index: '\a', Digest: []byte("E\xa8b\x1d4\xa5}\xf2\xb2\xe7\xf1L\x92\xb9\x9a\xc8\xde}X\x05"), DigestAlg: crypto.SHA1}, 86 | {Index: '\b', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 87 | {Index: '\t', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 88 | {Index: '\n', Digest: []byte("\x82\x84\x10>\x06\xd4\x01\"\xbcd\xa0䡉\x1a\xf9\xec\xd4\\\xf6"), DigestAlg: crypto.SHA1}, 89 | {Index: '\v', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 90 | {Index: '\f', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 91 | {Index: '\r', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 92 | {Index: '\x0e', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 93 | {Index: '\x0f', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 94 | {Index: '\x10', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 95 | {Index: '\x11', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA1}, 96 | {Index: '\x12', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA1}, 97 | {Index: '\x13', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA1}, 98 | {Index: '\x14', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA1}, 99 | {Index: '\x15', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA1}, 100 | {Index: '\x16', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA1}, 101 | {Index: '\x17', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA1}, 102 | {Index: '\x00', Digest: []byte("\xfc\xec\xb5j\xcc08b\xb3\x0e\xb3Bę\v\xebP\xb5ૉr$I\xc2٧?7\xb0\x19\xfe"), DigestAlg: crypto.SHA256}, 103 | {Index: '\x01', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 104 | {Index: '\x02', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 105 | {Index: '\x03', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 106 | {Index: '\x04', Digest: []byte("\xa9)h\x80oy_\xa3D5\xd9\xf1\x18\x13hL\xa1\xe7\x05`w\xf7\x00\xbaI\xf2o\x99b\xf8m\x89"), DigestAlg: crypto.SHA256}, 107 | {Index: '\x05', Digest: []byte("̆\x18\xb7y2\xb4\xef\xda\x12\xccX\xba\xd9>\xcdѕ\x9d\xea)\xe5\xabyE%\xa6\x19\xf5\xba\xab\xee"), DigestAlg: crypto.SHA256}, 108 | {Index: '\x06', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 109 | {Index: '\a', Digest: []byte("Q\xb3\x04\x88\xc9\xe6%]\x82+\xdc\x1b ٩,2\xbd\xe6\xc3\xe7\xbc\x02\xbc\xdd2\x82^\xb5\xef\x06\x9a"), DigestAlg: crypto.SHA256}, 110 | {Index: '\b', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 111 | {Index: '\t', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 112 | {Index: '\n', Digest: []byte("\xc3l\x9a\xb1\x10\x9b\xa0\x8a?dX!\x18\xf8G\x1a]i[\xc9#\xa0\xa2\xbd\x04]\xb1K\x97OB9"), DigestAlg: crypto.SHA256}, 113 | {Index: '\v', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 114 | {Index: '\f', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 115 | {Index: '\r', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 116 | {Index: '\x0e', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 117 | {Index: '\x0f', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 118 | {Index: '\x10', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 119 | {Index: '\x11', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA256}, 120 | {Index: '\x12', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA256}, 121 | {Index: '\x13', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA256}, 122 | {Index: '\x14', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA256}, 123 | {Index: '\x15', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA256}, 124 | {Index: '\x16', Digest: []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), DigestAlg: crypto.SHA256}, 125 | {Index: '\x17', Digest: []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), DigestAlg: crypto.SHA256}, 126 | } 127 | 128 | events, err := elr.Verify(register.PCRBank{TCGHashAlgo: state.HashAlgo_SHA1, PCRs: pcrs}.MRs()) 129 | if err != nil { 130 | t.Fatalf("failed to verify log: %v", err) 131 | } 132 | 133 | sbs, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig, extract.Opts{}) 134 | if err != nil { 135 | t.Fatalf("failed parsing secureboot state: %v", err) 136 | } 137 | if got, want := len(sbs.PostSeparatorAuthority), 3; got != want { 138 | t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want) 139 | } 140 | } 141 | 142 | func b64MustDecode(input string) []byte { 143 | b, err := base64.StdEncoding.DecodeString(input) 144 | if err != nil { 145 | panic(err) 146 | } 147 | return b 148 | } 149 | 150 | func TestSecureBootOptionRom(t *testing.T) { 151 | raw, err := os.ReadFile("../testdata/legacydata/option_rom_eventlog") 152 | if err != nil { 153 | t.Fatalf("reading test data: %v", err) 154 | } 155 | elr, err := tcg.ParseEventLog(raw, tcg.ParseOpts{}) 156 | if err != nil { 157 | t.Fatalf("parsing event log: %v", err) 158 | } 159 | 160 | pcrs := []register.PCR{ 161 | {Index: '\x00', Digest: b64MustDecode("AVGK7ch6DvUF0nJh74NYCefaAIY="), DigestAlg: crypto.SHA1}, 162 | {Index: '\x01', Digest: b64MustDecode("vr/0wIpmd0c6tgTO3vuC+FDN6IM="), DigestAlg: crypto.SHA1}, 163 | {Index: '\x02', Digest: b64MustDecode("NmoxoMB1No8OEIVzM+ou1uigD9M="), DigestAlg: crypto.SHA1}, 164 | {Index: '\x03', Digest: b64MustDecode("sqg7Dr8vg3Qpmlsr38MeqVWtcjY="), DigestAlg: crypto.SHA1}, 165 | {Index: '\x04', Digest: b64MustDecode("OfOIw5WekEaUcm9MAVttzq4GgKE="), DigestAlg: crypto.SHA1}, 166 | {Index: '\x05', Digest: b64MustDecode("cjoFIM9/KXhUh0K9FUFwayRGRZ4="), DigestAlg: crypto.SHA1}, 167 | {Index: '\x06', Digest: b64MustDecode("sqg7Dr8vg3Qpmlsr38MeqVWtcjY="), DigestAlg: crypto.SHA1}, 168 | {Index: '\x07', Digest: b64MustDecode("IN59+6a838ytrX4+sJnJHU2Xxa0="), DigestAlg: crypto.SHA1}, 169 | } 170 | 171 | events, err := elr.Verify(register.PCRBank{TCGHashAlgo: state.HashAlgo_SHA1, PCRs: pcrs}.MRs()) 172 | if err != nil { 173 | t.Errorf("failed to verify log: %v", err) 174 | } 175 | 176 | sbs, err := extract.ParseSecurebootState(events, extract.TPMRegisterConfig, extract.Opts{}) 177 | if err != nil { 178 | t.Errorf("failed parsing secureboot state: %v", err) 179 | } 180 | if got, want := len(sbs.PostSeparatorAuthority), 2; got != want { 181 | t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want) 182 | } 183 | 184 | if got, want := len(sbs.DriverLoadSourceHints), 1; got != want { 185 | t.Fatalf("len(sbs.DriverLoadSourceHints) = %d, want %d", got, want) 186 | } 187 | if got, want := sbs.DriverLoadSourceHints[0], extract.PciMmioSource; got != want { 188 | t.Errorf("sbs.DriverLoadSourceHints[0] = %v, want %v", got, want) 189 | } 190 | } 191 | 192 | func TestSecureBootEventLogUbuntu(t *testing.T) { 193 | data, err := os.ReadFile("../testdata/legacydata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog") 194 | if err != nil { 195 | t.Fatalf("reading test data: %v", err) 196 | } 197 | el, err := tcg.ParseEventLog(data, tcg.ParseOpts{}) 198 | if err != nil { 199 | t.Fatalf("parsing event log: %v", err) 200 | } 201 | evts := el.Events(register.HashSHA256) 202 | if err != nil { 203 | t.Fatalf("verifying event log: %v", err) 204 | } 205 | _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig, extract.Opts{}) 206 | if err != nil { 207 | t.Errorf("parsing sb state: %v", err) 208 | } 209 | } 210 | 211 | func TestSecureBootEventLogFedora36(t *testing.T) { 212 | data, err := os.ReadFile("../testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog") 213 | if err != nil { 214 | t.Fatalf("reading test data: %v", err) 215 | } 216 | el, err := tcg.ParseEventLog(data, tcg.ParseOpts{}) 217 | if err != nil { 218 | t.Fatalf("parsing event log: %v", err) 219 | } 220 | evts := el.Events(register.HashSHA256) 221 | if err != nil { 222 | t.Fatalf("verifying event log: %v", err) 223 | } 224 | _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig, extract.Opts{}) 225 | if err != nil { 226 | t.Errorf("parsing sb state: %v", err) 227 | } 228 | } 229 | 230 | func TestEncodeUEFIVariableData(t *testing.T) { 231 | data, err := os.ReadFile("../testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog") 232 | if err != nil { 233 | t.Fatalf("reading test data: %v", err) 234 | } 235 | el, err := tcg.ParseEventLog(data, tcg.ParseOpts{}) 236 | if err != nil { 237 | t.Fatalf("parsing event log: %v", err) 238 | } 239 | evts := el.Events(register.HashSHA256) 240 | if err != nil { 241 | t.Fatalf("verifying event log: %v", err) 242 | } 243 | for _, evt := range evts { 244 | if evt.Type != tcg.EFIVariableDriverConfig { 245 | continue 246 | } 247 | v, err := tcg.ParseUEFIVariableData(bytes.NewReader(evt.RawData())) 248 | if err != nil { 249 | t.Fatal(err) 250 | } 251 | data, err := v.Encode() 252 | if err != nil { 253 | t.Fatal(err) 254 | } 255 | newv, err := tcg.ParseUEFIVariableData(bytes.NewReader(data)) 256 | if err != nil { 257 | t.Errorf("failed to parse after encoding: %v", err) 258 | } 259 | if diff := cmp.Diff(v, newv); diff != "" { 260 | t.Errorf("Encode() produced different encodings: %v", diff) 261 | } 262 | } 263 | 264 | } 265 | 266 | func TestSecureBootAllowEmptySBVar(t *testing.T) { 267 | data, err := os.ReadFile("../testdata/legacydata/coreos_36_shielded_vm_no_secure_boot_eventlog") 268 | if err != nil { 269 | t.Fatalf("reading test data: %v", err) 270 | } 271 | el, err := tcg.ParseEventLog(data, tcg.ParseOpts{}) 272 | if err != nil { 273 | t.Fatalf("parsing event log: %v", err) 274 | } 275 | evts := el.Events(register.HashSHA256) 276 | if err != nil { 277 | t.Fatalf("verifying event log: %v", err) 278 | } 279 | tests := []struct { 280 | name string 281 | newVar []byte 282 | allowEmpty bool 283 | wantErr bool 284 | }{ 285 | { 286 | name: "emptyAllowed", 287 | allowEmpty: true, 288 | }, 289 | { 290 | name: "emptyNotAllowed", 291 | wantErr: true, 292 | }, 293 | { 294 | name: "1emptyAllowed", 295 | newVar: []byte{1}, 296 | allowEmpty: true, 297 | }, 298 | { 299 | name: "1emptyNotAllowed", 300 | newVar: []byte{1}, 301 | }, 302 | { 303 | name: "0emptyAllowed", 304 | newVar: []byte{0}, 305 | allowEmpty: true, 306 | }, 307 | { 308 | name: "0emptyNotAllowed", 309 | newVar: []byte{0}, 310 | }, 311 | { 312 | name: "len2emptyAllowed", 313 | newVar: []byte{0, 1}, 314 | allowEmpty: true, 315 | wantErr: true, 316 | }, 317 | { 318 | name: "len2emptyNotAllowed", 319 | newVar: []byte{0, 1}, 320 | wantErr: true, 321 | }, 322 | } 323 | 324 | for _, tt := range tests { 325 | t.Run(tt.name, func(t *testing.T) { 326 | for i, evt := range evts { 327 | if evt.Type != tcg.EFIVariableDriverConfig { 328 | continue 329 | } 330 | v, err := tcg.ParseUEFIVariableData(bytes.NewReader(evt.RawData())) 331 | if err != nil { 332 | t.Fatal(err) 333 | } 334 | if v.VarName() == "SecureBoot" { 335 | v.VariableData = tt.newVar 336 | } 337 | data, err := v.Encode() 338 | if err != nil { 339 | t.Fatal(err) 340 | } 341 | evt.Data = data 342 | dgst := sha256.Sum256(evt.Data) 343 | evt.Digest = dgst[:] 344 | evts[i] = evt 345 | } 346 | opts := extract.Opts{} 347 | if tt.allowEmpty { 348 | opts.AllowEmptySBVar = true 349 | } 350 | _, err = extract.ParseSecurebootState(evts, extract.TPMRegisterConfig, opts) 351 | if (err != nil) != tt.wantErr { 352 | t.Errorf("ParseSecurebootState() = %v, wantErr %v", err, tt.wantErr) 353 | 354 | } 355 | 356 | }) 357 | } 358 | 359 | } 360 | -------------------------------------------------------------------------------- /extract/extract.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package extract has tools for extracting boot and runtime information from measurements. 16 | package extract 17 | 18 | import ( 19 | "bytes" 20 | "crypto" 21 | "crypto/x509" 22 | "errors" 23 | "fmt" 24 | 25 | pb "github.com/google/go-eventlog/proto/state" 26 | "github.com/google/go-eventlog/tcg" 27 | "github.com/google/go-eventlog/wellknown" 28 | "github.com/google/go-tpm/legacy/tpm2" 29 | ) 30 | 31 | var ( 32 | newGrubKernelCmdlinePrefix = []byte("kernel_cmdline: ") 33 | oldGrubKernelCmdlinePrefix = []byte("grub_kernel_cmdline ") 34 | // See https://www.gnu.org/software/grub/manual/grub/grub.html#Measured-Boot. 35 | validPrefixes = [][]byte{[]byte("grub_cmd: "), 36 | newGrubKernelCmdlinePrefix, 37 | []byte("module_cmdline: "), 38 | // Older style prefixes: 39 | // https://src.fedoraproject.org/rpms/grub2/blob/c789522f7cfa19a10cd716a1db24dab5499c6e5c/f/0224-Rework-TPM-measurements.patch 40 | oldGrubKernelCmdlinePrefix, 41 | []byte("grub_cmd ")} 42 | ) 43 | 44 | // Bootloader refers to the second-stage bootloader that loads and transfers 45 | // execution to the OS kernel. 46 | type Bootloader int 47 | 48 | const ( 49 | // UnsupportedLoader refers to a second-stage bootloader that is of an 50 | // unsupported type. VerifyAttestation will not parse the PC Client Event 51 | // Log for bootloader events. 52 | UnsupportedLoader Bootloader = iota 53 | // GRUB (https://www.gnu.org/software/grub/). 54 | GRUB 55 | ) 56 | 57 | // Opts gives options for extracting information from an event log. 58 | type Opts struct { 59 | Loader Bootloader 60 | // AllowEmptySBVar allows the SecureBoot variable to be empty in addition to length 1 (0 or 1). 61 | // This can be used when the SecureBoot variable is not initialized. 62 | AllowEmptySBVar bool 63 | // AllowEFIAppBeforeCallingEvent skips a check that requires 64 | // EV_EFI_BOOT_SERVICES_APPLICATION to occur after a 65 | // "Calling EFI Application from Boot Option". This option is useful when 66 | // the host platform loads EFI Applications unrelated to OS boot. 67 | AllowEFIAppBeforeCallingEvent bool 68 | } 69 | 70 | // FirmwareLogState extracts event info from a verified TCG PC Client event 71 | // log into a FirmwareLogState. 72 | // It returns an error on failing to parse malformed events. 73 | // 74 | // The returned FirmwareLogState may be a partial FirmwareLogState. 75 | // In the case of a partially filled state, err will be non-nil. 76 | // Callers can look for individual errors using `errors.Is`. 77 | // 78 | // It is the caller's responsibility to ensure that the passed events have 79 | // been replayed (e.g., using `tcg.ParseAndReplay`) against a verified measurement 80 | // register bank. 81 | func FirmwareLogState(events []tcg.Event, hash crypto.Hash, registerCfg registerConfig, opts Opts) (*pb.FirmwareLogState, error) { 82 | var joined error 83 | tcgHash, err := tpm2.HashToAlgorithm(hash) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | platform, err := registerCfg.PlatformExtracter(hash, events) 89 | if err != nil { 90 | joined = errors.Join(joined, err) 91 | } 92 | sbState, err := SecureBootState(events, registerCfg, opts) 93 | if err != nil { 94 | joined = errors.Join(joined, err) 95 | } 96 | efiState, err := EfiState(hash, events, registerCfg, opts) 97 | 98 | if err != nil { 99 | joined = errors.Join(joined, err) 100 | } 101 | 102 | var grub *pb.GrubState 103 | var kernel *pb.LinuxKernelState 104 | if opts.Loader == GRUB { 105 | grub, err = registerCfg.GRUBExtracter(hash, events) 106 | 107 | if err != nil { 108 | joined = errors.Join(joined, err) 109 | } 110 | kernel, err = LinuxKernelStateFromGRUB(grub) 111 | if err != nil { 112 | joined = errors.Join(joined, err) 113 | } 114 | } 115 | return &pb.FirmwareLogState{ 116 | Platform: platform, 117 | SecureBoot: sbState, 118 | Efi: efiState, 119 | RawEvents: tcg.ConvertToPbEvents(hash, events), 120 | Hash: pb.HashAlgo(tcgHash), 121 | Grub: grub, 122 | LinuxKernel: kernel, 123 | LogType: registerCfg.LogType, 124 | }, joined 125 | } 126 | 127 | func contains(set [][]byte, value []byte) bool { 128 | for _, setItem := range set { 129 | if bytes.Equal(value, setItem) { 130 | return true 131 | } 132 | } 133 | return false 134 | } 135 | 136 | type separatorInfo struct { 137 | separatorData [][]byte 138 | separatorDigests [][]byte 139 | } 140 | 141 | // getSeparatorInfo is used to return the valid event data and their corresponding 142 | // digests. This is useful for events like separators, where the data is known 143 | // ahead of time. 144 | func getSeparatorInfo(hash crypto.Hash) *separatorInfo { 145 | hasher := hash.New() 146 | // From the PC Client Firmware Profile spec, on the separator event: 147 | // The event field MUST contain the hex value 00000000h or FFFFFFFFh. 148 | sepData := [][]byte{{0, 0, 0, 0}, {0xff, 0xff, 0xff, 0xff}} 149 | sepDigests := make([][]byte, 0, len(sepData)) 150 | for _, value := range sepData { 151 | hasher.Write(value) 152 | sepDigests = append(sepDigests, hasher.Sum(nil)) 153 | } 154 | return &separatorInfo{separatorData: sepData, separatorDigests: sepDigests} 155 | } 156 | 157 | // checkIfValidSeparator returns true if both the separator event's type and 158 | // digest match the expected event data. 159 | // If the event type is Separator, but the data is invalid, it returns false 160 | // and an error. 161 | // checkIfValidSeparator returns false and a nil error on other event types. 162 | func checkIfValidSeparator(event tcg.Event, sepInfo *separatorInfo) (bool, error) { 163 | evtType := event.UntrustedType() 164 | index := event.MRIndex() 165 | if (evtType != tcg.Separator) && !contains(sepInfo.separatorDigests, event.ReplayedDigest()) { 166 | return false, nil 167 | } 168 | // To make sure we have a valid event, we check any event (e.g., separator) 169 | // that claims to be of the event type or "looks like" the event to prevent 170 | // certain vulnerabilities in event parsing. For more info see: 171 | // https://github.com/google/go-attestation/blob/master/docs/event-log-disclosure.md 172 | if evtType != tcg.Separator { 173 | return false, fmt.Errorf("MR%d event contains separator data but non-separator type %d", index, evtType) 174 | } 175 | if !event.DigestVerified() { 176 | return false, fmt.Errorf("unverified separator digest for MR%d", index) 177 | } 178 | if !contains(sepInfo.separatorData, event.RawData()) { 179 | return false, fmt.Errorf("invalid separator data for MR%d", index) 180 | } 181 | return true, nil 182 | } 183 | 184 | func convertToPbDatabase(certs []x509.Certificate, hashes [][]byte) *pb.Database { 185 | protoCerts := make([]*pb.Certificate, 0, len(certs)) 186 | for _, cert := range certs { 187 | wkEnum, err := matchWellKnown(cert) 188 | var pbCert pb.Certificate 189 | if err == nil { 190 | pbCert.Representation = &pb.Certificate_WellKnown{WellKnown: wkEnum} 191 | } else { 192 | pbCert.Representation = &pb.Certificate_Der{Der: cert.Raw} 193 | } 194 | protoCerts = append(protoCerts, &pbCert) 195 | } 196 | return &pb.Database{ 197 | Certs: protoCerts, 198 | Hashes: hashes, 199 | } 200 | } 201 | 202 | func matchWellKnown(cert x509.Certificate) (pb.WellKnownCertificate, error) { 203 | if bytes.Equal(wellknown.WindowsProductionPCA2011Cert, cert.Raw) { 204 | return pb.WellKnownCertificate_MS_WINDOWS_PROD_PCA_2011, nil 205 | } 206 | if bytes.Equal(wellknown.MicrosoftUEFICA2011Cert, cert.Raw) { 207 | return pb.WellKnownCertificate_MS_THIRD_PARTY_UEFI_CA_2011, nil 208 | } 209 | if bytes.Equal(wellknown.MicrosoftKEKCA2011Cert, cert.Raw) { 210 | return pb.WellKnownCertificate_MS_THIRD_PARTY_KEK_CA_2011, nil 211 | } 212 | if bytes.Equal(wellknown.GceDefaultPKCert, cert.Raw) { 213 | return pb.WellKnownCertificate_GCE_DEFAULT_PK, nil 214 | } 215 | return pb.WellKnownCertificate_UNKNOWN, errors.New("failed to find matching well known certificate") 216 | } 217 | 218 | // SecureBootState extracts Secure Boot information from a UEFI TCG2 219 | // firmware event log. 220 | func SecureBootState(replayEvents []tcg.Event, registerCfg registerConfig, opts Opts) (*pb.SecureBootState, error) { 221 | attestSbState, err := ParseSecurebootState(replayEvents, registerCfg, opts) 222 | if err != nil { 223 | return nil, fmt.Errorf("failed to parse SecureBootState: %v", err) 224 | } 225 | if len(attestSbState.PreSeparatorAuthority) != 0 { 226 | return nil, fmt.Errorf("event log contained %v pre-separator authorities, which are not expected or supported", len(attestSbState.PreSeparatorAuthority)) 227 | } 228 | return &pb.SecureBootState{ 229 | Enabled: attestSbState.Enabled, 230 | Db: convertToPbDatabase(attestSbState.PermittedKeys, attestSbState.PermittedHashes), 231 | Dbx: convertToPbDatabase(attestSbState.ForbiddenKeys, attestSbState.ForbiddenHashes), 232 | Authority: convertToPbDatabase(attestSbState.PostSeparatorAuthority, nil), 233 | Pk: convertToPbDatabase(attestSbState.PlatformKeys, attestSbState.PlatformKeyHashes), 234 | Kek: convertToPbDatabase(attestSbState.ExchangeKeys, attestSbState.ExchangeKeyHashes), 235 | }, nil 236 | } 237 | 238 | // EfiDriverState extracts EFI Driver information from a UEFI TCG2 firmware event log. 239 | // Obtained from section 3.3.4.3 PCR[2]-UEFI Drivers and UEFI Applications 240 | // https://trustedcomputinggroup.org/wp-content/uploads/TCG-PC-Client-Platform-Firmware-Profile-Version-1.06-Revision-52_pub-3.pdf 241 | func EfiDriverState(events []tcg.Event, registerCfg registerConfig) (*pb.EfiState, error) { 242 | var ( 243 | seenSeparator bool 244 | efiDriverStates []*pb.EfiApp 245 | efiRuntimeDriverStates []*pb.EfiApp 246 | ) 247 | for _, e := range events { 248 | if e.MRIndex() != registerCfg.FirmwareDriverIdx { 249 | continue 250 | } 251 | 252 | et, err := tcg.UntrustedParseEventType(uint32(e.UntrustedType())) 253 | if err != nil { 254 | return nil, fmt.Errorf("unrecognised event type: %v", err) 255 | } 256 | digestVerify := DigestEquals(e, e.RawData()) 257 | switch et { 258 | case tcg.Separator: 259 | if seenSeparator { 260 | return nil, fmt.Errorf("duplicate separator at event %d", e.Num()) 261 | } 262 | seenSeparator = true 263 | if !bytes.Equal(e.RawData(), []byte{0, 0, 0, 0}) { 264 | return nil, fmt.Errorf("invalid separator data at event %d: %v", e.Num(), e.RawData()) 265 | } 266 | if digestVerify != nil { 267 | return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.Num(), digestVerify) 268 | } 269 | 270 | case tcg.EFIBootServicesDriver: 271 | if !seenSeparator { 272 | // The EFI Boot Services Driver will use the EFI LoadImage service, so try loading it. 273 | _, err := tcg.ParseEFIImageLoad(bytes.NewReader(e.RawData())) 274 | if err != nil { 275 | return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.Num(), err) 276 | } 277 | efiDriverStates = append(efiDriverStates, &pb.EfiApp{Digest: e.ReplayedDigest()}) 278 | } 279 | case tcg.EFIRuntimeServicesDriver: 280 | if !seenSeparator { 281 | // The EFI Runtime Services Driver will use the EFI LoadImage service, so try loading it. 282 | _, err := tcg.ParseEFIImageLoad(bytes.NewReader(e.RawData())) 283 | if err != nil { 284 | return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.Num(), err) 285 | } 286 | efiRuntimeDriverStates = append(efiRuntimeDriverStates, &pb.EfiApp{Digest: e.ReplayedDigest()}) 287 | } 288 | } 289 | } 290 | return &pb.EfiState{ 291 | BootServicesDrivers: efiDriverStates, 292 | RuntimeServicesDrivers: efiRuntimeDriverStates, 293 | }, nil 294 | } 295 | 296 | // PlatformState extracts platform information from a UEFI TCG2 firmware 297 | // event log. 298 | func PlatformState(hash crypto.Hash, events []tcg.Event) (*pb.PlatformState, error) { 299 | // We pre-compute the separator and EFI Action event hash. 300 | // We check if these events have been modified, since the event type is 301 | // untrusted. 302 | sepInfo := getSeparatorInfo(hash) 303 | var versionString []byte 304 | var nonHostInfo []byte 305 | for _, event := range events { 306 | index := event.MRIndex() 307 | if index != 0 { 308 | continue 309 | } 310 | evtType := event.UntrustedType() 311 | isSeparator, err := checkIfValidSeparator(event, sepInfo) 312 | if err != nil { 313 | return nil, err 314 | } 315 | if isSeparator { 316 | // Don't trust any PCR0 events after the separator 317 | break 318 | } 319 | 320 | if evtType == tcg.SCRTMVersion { 321 | if !event.DigestVerified() { 322 | return nil, fmt.Errorf("invalid SCRTM version event for PCR%d", index) 323 | } 324 | versionString = event.RawData() 325 | } 326 | 327 | if evtType == tcg.NonhostInfo { 328 | if !event.DigestVerified() { 329 | return nil, fmt.Errorf("invalid Non-Host info event for PCR%d", index) 330 | } 331 | nonHostInfo = event.RawData() 332 | } 333 | } 334 | 335 | state := &pb.PlatformState{} 336 | if gceVersion, err := wellknown.ConvertSCRTMVersionToGCEFirmwareVersion(versionString); err == nil { 337 | state.Firmware = &pb.PlatformState_GceVersion{GceVersion: gceVersion} 338 | } else { 339 | state.Firmware = &pb.PlatformState_ScrtmVersionId{ScrtmVersionId: versionString} 340 | } 341 | 342 | if tech, err := wellknown.ParseGCENonHostInfo(nonHostInfo); err == nil { 343 | state.Technology = tech 344 | } 345 | 346 | return state, nil 347 | } 348 | 349 | // EfiState extracts EFI app information from a UEFI TCG2 firmware 350 | // event log. 351 | func EfiState(hash crypto.Hash, events []tcg.Event, registerCfg registerConfig, opts Opts) (*pb.EfiState, error) { 352 | // We pre-compute various event digests, and check if those event type have 353 | // been modified. We only trust events that come before the 354 | // ExitBootServices() request. 355 | separatorInfo := getSeparatorInfo(hash) 356 | 357 | hasher := hash.New() 358 | hasher.Write([]byte(tcg.CallingEFIApplication)) 359 | callingEFIAppDigest := hasher.Sum(nil) 360 | 361 | hasher.Reset() 362 | hasher.Write([]byte(tcg.ExitBootServicesInvocation)) 363 | exitBootSvcDigest := hasher.Sum(nil) 364 | 365 | var efiAppStates []*pb.EfiApp 366 | var seenSeparator4 bool 367 | var seenSeparator5 bool 368 | var seenCallingEfiApp bool 369 | var seenExitBootServices bool 370 | for _, event := range events { 371 | index := event.MRIndex() 372 | // MRs corresponding to EFI apps and the Exit Boot Services event. 373 | if index != registerCfg.EFIAppIdx && index != registerCfg.ExitBootServicesIdx { 374 | continue 375 | } 376 | evtType := event.UntrustedType() 377 | 378 | // Switch statements won't work since duplicate cases will get triggered like an if, else-if, else. // Process Calling EFI Application event. 379 | // See https://github.com/golang/go/commit/2d9378c7f6dfbbe82d1bbd806093c2dfe57d7e17 380 | // PCRs use different indexes, but RTMRs do not. 381 | if index == registerCfg.EFIAppIdx { 382 | if bytes.Equal(callingEFIAppDigest, event.ReplayedDigest()) { 383 | if evtType != tcg.EFIAction { 384 | return nil, fmt.Errorf("%s%d contains CallingEFIApp event but non EFIAction type: %d", 385 | registerCfg.Name, index, evtType) 386 | } 387 | if !event.DigestVerified() { 388 | return nil, fmt.Errorf("unverified CallingEFIApp digest for %s%d", registerCfg.Name, index) 389 | } 390 | // We don't support calling more than one boot device. 391 | if seenCallingEfiApp { 392 | return nil, fmt.Errorf("found duplicate CallingEFIApp event in %s%d", registerCfg.Name, index) 393 | } 394 | if seenSeparator4 { 395 | return nil, fmt.Errorf("found CallingEFIApp event in %s%d after separator event", registerCfg.Name, index) 396 | } 397 | seenCallingEfiApp = true 398 | } 399 | 400 | if evtType == tcg.EFIBootServicesApplication { 401 | if !opts.AllowEFIAppBeforeCallingEvent && !seenCallingEfiApp { 402 | return nil, fmt.Errorf("found EFIBootServicesApplication in %s%d before CallingEFIApp event", registerCfg.Name, index) 403 | } 404 | efiAppStates = append(efiAppStates, &pb.EfiApp{Digest: event.ReplayedDigest()}) 405 | } 406 | 407 | isSeparator, err := checkIfValidSeparator(event, separatorInfo) 408 | if err != nil { 409 | return nil, err 410 | } 411 | if isSeparator { 412 | if seenSeparator4 { 413 | return nil, fmt.Errorf("found duplicate Separator event in %s%d", registerCfg.Name, registerCfg.EFIAppIdx) 414 | } 415 | seenSeparator4 = true 416 | } 417 | } 418 | if index == registerCfg.ExitBootServicesIdx { 419 | // Process ExitBootServices event. 420 | if bytes.Equal(exitBootSvcDigest, event.ReplayedDigest()) { 421 | if evtType != tcg.EFIAction { 422 | return nil, fmt.Errorf("%s%d contains ExitBootServices event but non EFIAction type: %d", 423 | registerCfg.Name, index, evtType) 424 | } 425 | if !event.DigestVerified() { 426 | return nil, fmt.Errorf("unverified ExitBootServices digest for %s%d", registerCfg.Name, index) 427 | } 428 | // Don't process any events after Boot Manager has requested 429 | // ExitBootServices(). 430 | seenExitBootServices = true 431 | break 432 | } 433 | 434 | isSeparator, err := checkIfValidSeparator(event, separatorInfo) 435 | if err != nil { 436 | return nil, err 437 | } 438 | if isSeparator { 439 | if seenSeparator5 { 440 | return nil, fmt.Errorf("found duplicate Separator event in %s%d", registerCfg.Name, registerCfg.ExitBootServicesIdx) 441 | } 442 | seenSeparator5 = true 443 | } 444 | } 445 | } 446 | // Only write EFI digests if we see an ExitBootServices invocation. 447 | // Otherwise, software further down the bootchain could extend bad 448 | // PCR4/RTMR2 measurements. 449 | if seenExitBootServices { 450 | efiDriver, err := EfiDriverState(events, registerCfg) 451 | if err != nil { 452 | return nil, err 453 | } 454 | return &pb.EfiState{ 455 | Apps: efiAppStates, 456 | BootServicesDrivers: efiDriver.BootServicesDrivers, 457 | RuntimeServicesDrivers: efiDriver.RuntimeServicesDrivers, 458 | }, nil 459 | } 460 | return nil, nil 461 | } 462 | 463 | // LinuxKernelStateFromGRUB extracts the kernel command line from GrubState. 464 | func LinuxKernelStateFromGRUB(grub *pb.GrubState) (*pb.LinuxKernelState, error) { 465 | var cmdline string 466 | seen := false 467 | 468 | for _, command := range grub.GetCommands() { 469 | // GRUB config is always in UTF-8: https://www.gnu.org/software/grub/manual/grub/html_node/Internationalisation.html. 470 | cmdBytes := []byte(command) 471 | suffixAt := getGrubKernelCmdlineSuffix(cmdBytes) 472 | if suffixAt == -1 { 473 | continue 474 | } 475 | 476 | if seen { 477 | return nil, fmt.Errorf("more than one kernel commandline in GRUB commands") 478 | } 479 | seen = true 480 | cmdline = command[suffixAt:] 481 | } 482 | 483 | return &pb.LinuxKernelState{CommandLine: cmdline}, nil 484 | } 485 | 486 | func getGrubKernelCmdlineSuffix(grubCmd []byte) int { 487 | for _, prefix := range [][]byte{oldGrubKernelCmdlinePrefix, newGrubKernelCmdlinePrefix} { 488 | if bytes.HasPrefix(grubCmd, prefix) { 489 | return len(prefix) 490 | } 491 | } 492 | return -1 493 | } 494 | --------------------------------------------------------------------------------