metropolis: unify utility packages

One last sweeping rename / reshuffle.

We get rid of //metropolis/node/common and //golibs, unifying them into
a single //metropolis/pkg meta-package.

This is to be documented somwhere properly, but here's the new logic
behind selecting where to place a new library package:

 - if it's specific to k8s-on-metropolis, put it in
   //metropolis/node/kubernetes/*. This is a self-contained tree that
   other paths cannot import from.
 - if it's a big new subsystem of the metropolis core, put it in
   //metropolis/node/core. This can be imported by anything in
   //m/n (eg the Kubernetes code at //m/n/kubernetes
 - otherwise, treat it as generic library that's part of the metropolis
   project, and put it in //metropolis/pkg. This can be imported by
   anything within //metropolis.

This will be followed up by a diff that updates visibility rules.

Test Plan: Pure refactor, CI only.

X-Origin-Diff: phab/D683
GitOrigin-RevId: 883e7f09a7d22d64e966d07bbe839454ed081c79
diff --git a/metropolis/pkg/tpm/eventlog/BUILD.bazel b/metropolis/pkg/tpm/eventlog/BUILD.bazel
new file mode 100644
index 0000000..94a7ee9
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/BUILD.bazel
@@ -0,0 +1,17 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "compat.go",
+        "eventlog.go",
+        "secureboot.go",
+    ],
+    importpath = "git.monogon.dev/source/nexantic.git/metropolis/pkg/tpm/eventlog",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//metropolis/pkg/tpm/eventlog/internal:go_default_library",
+        "@com_github_google_certificate_transparency_go//x509:go_default_library",
+        "@com_github_google_go_tpm//tpm2:go_default_library",
+    ],
+)
diff --git a/metropolis/pkg/tpm/eventlog/LICENSE-3RD-PARTY.txt b/metropolis/pkg/tpm/eventlog/LICENSE-3RD-PARTY.txt
new file mode 100644
index 0000000..2d3298c
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/LICENSE-3RD-PARTY.txt
@@ -0,0 +1,12 @@
+Copyright 2020 Google Inc.
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
\ No newline at end of file
diff --git a/metropolis/pkg/tpm/eventlog/compat.go b/metropolis/pkg/tpm/eventlog/compat.go
new file mode 100644
index 0000000..f83972b
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/compat.go
@@ -0,0 +1,32 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package eventlog
+
+// This file contains compatibility functions for our TPM library
+
+import (
+	"crypto"
+)
+
+// ConvertRawPCRs converts from raw PCRs to eventlog PCR structures
+func ConvertRawPCRs(pcrs [][]byte) []PCR {
+	var evPCRs []PCR
+	for i, digest := range pcrs {
+		evPCRs = append(evPCRs, PCR{DigestAlg: crypto.SHA256, Index: i, Digest: digest})
+	}
+	return evPCRs
+}
diff --git a/metropolis/pkg/tpm/eventlog/eventlog.go b/metropolis/pkg/tpm/eventlog/eventlog.go
new file mode 100644
index 0000000..49a8a26
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/eventlog.go
@@ -0,0 +1,646 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Taken and pruned from go-attestation revision 2453c8f39a4ff46009f6a9db6fb7c6cca789d9a1 under Apache 2.0
+
+package eventlog
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/sha1"
+	"crypto/sha256"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"sort"
+
+	// Ensure hashes are available.
+	_ "crypto/sha256"
+
+	"github.com/google/go-tpm/tpm2"
+)
+
+// HashAlg identifies a hashing Algorithm.
+type HashAlg uint8
+
+// Valid hash algorithms.
+var (
+	HashSHA1   = HashAlg(tpm2.AlgSHA1)
+	HashSHA256 = HashAlg(tpm2.AlgSHA256)
+)
+
+func (a HashAlg) cryptoHash() crypto.Hash {
+	switch a {
+	case HashSHA1:
+		return crypto.SHA1
+	case HashSHA256:
+		return crypto.SHA256
+	}
+	return 0
+}
+
+func (a HashAlg) goTPMAlg() tpm2.Algorithm {
+	switch a {
+	case HashSHA1:
+		return tpm2.AlgSHA1
+	case HashSHA256:
+		return tpm2.AlgSHA256
+	}
+	return 0
+}
+
+// String returns a human-friendly representation of the hash algorithm.
+func (a HashAlg) String() string {
+	switch a {
+	case HashSHA1:
+		return "SHA1"
+	case HashSHA256:
+		return "SHA256"
+	}
+	return fmt.Sprintf("HashAlg<%d>", int(a))
+}
+
+// ReplayError describes the parsed events that failed to verify against
+// a particular PCR.
+type ReplayError struct {
+	Events      []Event
+	invalidPCRs []int
+}
+
+func (e ReplayError) affected(pcr int) bool {
+	for _, p := range e.invalidPCRs {
+		if p == pcr {
+			return true
+		}
+	}
+	return false
+}
+
+// Error returns a human-friendly description of replay failures.
+func (e ReplayError) Error() string {
+	return fmt.Sprintf("event log failed to verify: the following registers failed to replay: %v", e.invalidPCRs)
+}
+
+// TPM algorithms. See the TPM 2.0 specification section 6.3.
+//
+// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf#page=42
+const (
+	algSHA1   uint16 = 0x0004
+	algSHA256 uint16 = 0x000B
+)
+
+// EventType indicates what kind of data an event is reporting.
+type EventType uint32
+
+// Event is a single event from a TCG event log. This reports descrete items such
+// as BIOs measurements or EFI states.
+type Event struct {
+	// order of the event in the event log.
+	sequence int
+
+	// PCR index of the event.
+	Index int
+	// Type of the event.
+	Type EventType
+
+	// Data of the event. For certain kinds of events, this must match the event
+	// digest to be valid.
+	Data []byte
+	// Digest is the verified digest of the event data. While an event can have
+	// multiple for different hash values, this is the one that was matched to the
+	// PCR value.
+	Digest []byte
+
+	// TODO(ericchiang): Provide examples or links for which event types must
+	// match their data to their digest.
+}
+
+func (e *Event) digestEquals(b []byte) error {
+	if len(e.Digest) == 0 {
+		return errors.New("no digests present")
+	}
+
+	switch len(e.Digest) {
+	case crypto.SHA256.Size():
+		s := sha256.Sum256(b)
+		if bytes.Equal(s[:], e.Digest) {
+			return nil
+		}
+	case crypto.SHA1.Size():
+		s := sha1.Sum(b)
+		if bytes.Equal(s[:], e.Digest) {
+			return nil
+		}
+	default:
+		return fmt.Errorf("cannot compare hash of length %d", len(e.Digest))
+	}
+
+	return fmt.Errorf("digest (len %d) does not match", len(e.Digest))
+}
+
+// EventLog is a parsed measurement log. This contains unverified data representing
+// boot events that must be replayed against PCR values to determine authenticity.
+type EventLog struct {
+	// Algs holds the set of algorithms that the event log uses.
+	Algs []HashAlg
+
+	rawEvents []rawEvent
+}
+
+func (e *EventLog) clone() *EventLog {
+	out := EventLog{
+		Algs:      make([]HashAlg, len(e.Algs)),
+		rawEvents: make([]rawEvent, len(e.rawEvents)),
+	}
+	copy(out.Algs, e.Algs)
+	copy(out.rawEvents, e.rawEvents)
+	return &out
+}
+
+type elWorkaround struct {
+	id          string
+	affectedPCR int
+	apply       func(e *EventLog) error
+}
+
+// inject3 appends two new events into the event log.
+func inject3(e *EventLog, pcr int, data1, data2, data3 string) error {
+	if err := inject(e, pcr, data1); err != nil {
+		return err
+	}
+	if err := inject(e, pcr, data2); err != nil {
+		return err
+	}
+	return inject(e, pcr, data3)
+}
+
+// inject2 appends two new events into the event log.
+func inject2(e *EventLog, pcr int, data1, data2 string) error {
+	if err := inject(e, pcr, data1); err != nil {
+		return err
+	}
+	return inject(e, pcr, data2)
+}
+
+// inject appends a new event into the event log.
+func inject(e *EventLog, pcr int, data string) error {
+	evt := rawEvent{
+		data:     []byte(data),
+		index:    pcr,
+		sequence: e.rawEvents[len(e.rawEvents)-1].sequence + 1,
+	}
+	for _, alg := range e.Algs {
+		h := alg.cryptoHash().New()
+		h.Write([]byte(data))
+		evt.digests = append(evt.digests, digest{hash: alg.cryptoHash(), data: h.Sum(nil)})
+	}
+	e.rawEvents = append(e.rawEvents, evt)
+	return nil
+}
+
+const (
+	ebsInvocation = "Exit Boot Services Invocation"
+	ebsSuccess    = "Exit Boot Services Returned with Success"
+	ebsFailure    = "Exit Boot Services Returned with Failure"
+)
+
+var eventlogWorkarounds = []elWorkaround{
+	{
+		id:          "EBS Invocation + Success",
+		affectedPCR: 5,
+		apply: func(e *EventLog) error {
+			return inject2(e, 5, ebsInvocation, ebsSuccess)
+		},
+	},
+	{
+		id:          "EBS Invocation + Failure",
+		affectedPCR: 5,
+		apply: func(e *EventLog) error {
+			return inject2(e, 5, ebsInvocation, ebsFailure)
+		},
+	},
+	{
+		id:          "EBS Invocation + Failure + Success",
+		affectedPCR: 5,
+		apply: func(e *EventLog) error {
+			return inject3(e, 5, ebsInvocation, ebsFailure, ebsSuccess)
+		},
+	},
+}
+
+// Verify replays the event log against a TPM's PCR values, returning the
+// events which could be matched to a provided PCR value.
+// An error is returned if the replayed digest for events with a given PCR
+// index do not match any provided value for that PCR index.
+func (e *EventLog) Verify(pcrs []PCR) ([]Event, error) {
+	events, err := e.verify(pcrs)
+	// If there were any issues replaying the PCRs, try each of the workarounds
+	// in turn.
+	// TODO(jsonp): Allow workarounds to be combined.
+	if rErr, isReplayErr := err.(ReplayError); isReplayErr {
+		for _, wkrd := range eventlogWorkarounds {
+			if !rErr.affected(wkrd.affectedPCR) {
+				continue
+			}
+			el := e.clone()
+			if err := wkrd.apply(el); err != nil {
+				return nil, fmt.Errorf("failed applying workaround %q: %v", wkrd.id, err)
+			}
+			if events, err := el.verify(pcrs); err == nil {
+				return events, nil
+			}
+		}
+	}
+
+	return events, err
+}
+
+// PCR encapsulates the value of a PCR at a point in time.
+type PCR struct {
+	Index     int
+	Digest    []byte
+	DigestAlg crypto.Hash
+}
+
+func (e *EventLog) verify(pcrs []PCR) ([]Event, error) {
+	events, err := replayEvents(e.rawEvents, pcrs)
+	if err != nil {
+		if _, isReplayErr := err.(ReplayError); isReplayErr {
+			return nil, err
+		}
+		return nil, fmt.Errorf("pcrs failed to replay: %v", err)
+	}
+	return events, nil
+}
+
+func extend(pcr PCR, replay []byte, e rawEvent) (pcrDigest []byte, eventDigest []byte, err error) {
+	h := pcr.DigestAlg
+
+	for _, digest := range e.digests {
+		if digest.hash != pcr.DigestAlg {
+			continue
+		}
+		if len(digest.data) != len(pcr.Digest) {
+			return nil, nil, fmt.Errorf("digest data length (%d) doesn't match PCR digest length (%d)", len(digest.data), len(pcr.Digest))
+		}
+		hash := h.New()
+		if len(replay) != 0 {
+			hash.Write(replay)
+		} else {
+			b := make([]byte, h.Size())
+			hash.Write(b)
+		}
+		hash.Write(digest.data)
+		return hash.Sum(nil), digest.data, nil
+	}
+	return nil, nil, fmt.Errorf("no event digest matches pcr algorithm: %v", pcr.DigestAlg)
+}
+
+// replayPCR replays the event log for a specific PCR, using pcr and
+// event digests with the algorithm in pcr. An error is returned if the
+// replayed values do not match the final PCR digest, or any event tagged
+// with that PCR does not posess an event digest with the specified algorithm.
+func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
+	var (
+		replay    []byte
+		outEvents []Event
+	)
+
+	for _, e := range rawEvents {
+		if e.index != pcr.Index {
+			continue
+		}
+
+		replayValue, digest, err := extend(pcr, replay, e)
+		if err != nil {
+			return nil, false
+		}
+		replay = replayValue
+		outEvents = append(outEvents, Event{sequence: e.sequence, Data: e.data, Digest: digest, Index: pcr.Index, Type: e.typ})
+	}
+
+	if len(outEvents) > 0 && !bytes.Equal(replay, pcr.Digest) {
+		return nil, false
+	}
+	return outEvents, true
+}
+
+type pcrReplayResult struct {
+	events     []Event
+	successful bool
+}
+
+func replayEvents(rawEvents []rawEvent, pcrs []PCR) ([]Event, error) {
+	var (
+		invalidReplays []int
+		verifiedEvents []Event
+		allPCRReplays  = map[int][]pcrReplayResult{}
+	)
+
+	// Replay the event log for every PCR and digest algorithm combination.
+	for _, pcr := range pcrs {
+		events, ok := replayPCR(rawEvents, pcr)
+		allPCRReplays[pcr.Index] = append(allPCRReplays[pcr.Index], pcrReplayResult{events, ok})
+	}
+
+	// Record PCR indices which do not have any successful replay. Record the
+	// events for a successful replay.
+pcrLoop:
+	for i, replaysForPCR := range allPCRReplays {
+		for _, replay := range replaysForPCR {
+			if replay.successful {
+				// We consider the PCR verified at this stage: The replay of values with
+				// one digest algorithm matched a provided value.
+				// As such, we save the PCR's events, and proceed to the next PCR.
+				verifiedEvents = append(verifiedEvents, replay.events...)
+				continue pcrLoop
+			}
+		}
+		invalidReplays = append(invalidReplays, i)
+	}
+
+	if len(invalidReplays) > 0 {
+		events := make([]Event, 0, len(rawEvents))
+		for _, e := range rawEvents {
+			events = append(events, Event{e.sequence, e.index, e.typ, e.data, nil})
+		}
+		return nil, ReplayError{
+			Events:      events,
+			invalidPCRs: invalidReplays,
+		}
+	}
+
+	sort.Slice(verifiedEvents, func(i int, j int) bool {
+		return verifiedEvents[i].sequence < verifiedEvents[j].sequence
+	})
+	return verifiedEvents, nil
+}
+
+// EV_NO_ACTION is a special event type that indicates information to the parser
+// instead of holding a measurement. For TPM 2.0, this event type is used to signal
+// switching from SHA1 format to a variable length digest.
+//
+// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110
+const eventTypeNoAction = 0x03
+
+// ParseEventLog parses an unverified measurement log.
+func ParseEventLog(measurementLog []byte) (*EventLog, error) {
+	var specID *specIDEvent
+	r := bytes.NewBuffer(measurementLog)
+	parseFn := parseRawEvent
+	var el EventLog
+	e, err := parseFn(r, specID)
+	if err != nil {
+		return nil, fmt.Errorf("parse first event: %v", err)
+	}
+	if e.typ == eventTypeNoAction {
+		specID, err = parseSpecIDEvent(e.data)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse spec ID event: %v", err)
+		}
+		for _, alg := range specID.algs {
+			switch tpm2.Algorithm(alg.ID) {
+			case tpm2.AlgSHA1:
+				el.Algs = append(el.Algs, HashSHA1)
+			case tpm2.AlgSHA256:
+				el.Algs = append(el.Algs, HashSHA256)
+			}
+		}
+		if len(el.Algs) == 0 {
+			return nil, fmt.Errorf("measurement log didn't use sha1 or sha256 digests")
+		}
+		// Switch to parsing crypto agile events. Don't include this in the
+		// replayed events since it intentionally doesn't extend the PCRs.
+		//
+		// Note that this doesn't actually guarentee that events have SHA256
+		// digests.
+		parseFn = parseRawEvent2
+	} else {
+		el.Algs = []HashAlg{HashSHA1}
+		el.rawEvents = append(el.rawEvents, e)
+	}
+	sequence := 1
+	for r.Len() != 0 {
+		e, err := parseFn(r, specID)
+		if err != nil {
+			return nil, err
+		}
+		e.sequence = sequence
+		sequence++
+		el.rawEvents = append(el.rawEvents, e)
+	}
+	return &el, nil
+}
+
+type specIDEvent struct {
+	algs []specAlgSize
+}
+
+type specAlgSize struct {
+	ID   uint16
+	Size uint16
+}
+
+// Expected values for various Spec ID Event fields.
+// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=19
+var wantSignature = [16]byte{0x53, 0x70,
+	0x65, 0x63, 0x20, 0x49,
+	0x44, 0x20, 0x45, 0x76,
+	0x65, 0x6e, 0x74, 0x30,
+	0x33, 0x00} // "Spec ID Event03\0"
+
+const (
+	wantMajor  = 2
+	wantMinor  = 0
+	wantErrata = 0
+)
+
+// parseSpecIDEvent parses a TCG_EfiSpecIDEventStruct structure from the reader.
+//
+// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=18
+func parseSpecIDEvent(b []byte) (*specIDEvent, error) {
+	r := bytes.NewReader(b)
+	var header struct {
+		Signature     [16]byte
+		PlatformClass uint32
+		VersionMinor  uint8
+		VersionMajor  uint8
+		Errata        uint8
+		UintnSize     uint8
+		NumAlgs       uint32
+	}
+	if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
+		return nil, fmt.Errorf("reading event header: %v", err)
+	}
+	if header.Signature != wantSignature {
+		return nil, fmt.Errorf("invalid spec id signature: %x", header.Signature)
+	}
+	if header.VersionMajor != wantMajor {
+		return nil, fmt.Errorf("invalid spec major version, got %02x, wanted %02x",
+			header.VersionMajor, wantMajor)
+	}
+	if header.VersionMinor != wantMinor {
+		return nil, fmt.Errorf("invalid spec minor version, got %02x, wanted %02x",
+			header.VersionMajor, wantMinor)
+	}
+
+	// TODO(ericchiang): Check errata? Or do we expect that to change in ways
+	// we're okay with?
+
+	specAlg := specAlgSize{}
+	e := specIDEvent{}
+	for i := 0; i < int(header.NumAlgs); i++ {
+		if err := binary.Read(r, binary.LittleEndian, &specAlg); err != nil {
+			return nil, fmt.Errorf("reading algorithm: %v", err)
+		}
+		e.algs = append(e.algs, specAlg)
+	}
+
+	var vendorInfoSize uint8
+	if err := binary.Read(r, binary.LittleEndian, &vendorInfoSize); err != nil {
+		return nil, fmt.Errorf("reading vender info size: %v", err)
+	}
+	if r.Len() != int(vendorInfoSize) {
+		return nil, fmt.Errorf("reading vendor info, expected %d remaining bytes, got %d", vendorInfoSize, r.Len())
+	}
+	return &e, nil
+}
+
+type digest struct {
+	hash crypto.Hash
+	data []byte
+}
+
+type rawEvent struct {
+	sequence int
+	index    int
+	typ      EventType
+	data     []byte
+	digests  []digest
+}
+
+// TPM 1.2 event log format. See "5.1 SHA1 Event Log Entry Format"
+// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
+type rawEventHeader struct {
+	PCRIndex  uint32
+	Type      uint32
+	Digest    [20]byte
+	EventSize uint32
+}
+
+type eventSizeErr struct {
+	eventSize uint32
+	logSize   int
+}
+
+func (e *eventSizeErr) Error() string {
+	return fmt.Sprintf("event data size (%d bytes) is greater than remaining measurement log (%d bytes)", e.eventSize, e.logSize)
+}
+
+func parseRawEvent(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
+	var h rawEventHeader
+	if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
+		return event, err
+	}
+	if h.EventSize == 0 {
+		return event, errors.New("event data size is 0")
+	}
+	if h.EventSize > uint32(r.Len()) {
+		return event, &eventSizeErr{h.EventSize, r.Len()}
+	}
+
+	data := make([]byte, int(h.EventSize))
+	if _, err := io.ReadFull(r, data); err != nil {
+		return event, err
+	}
+
+	digests := []digest{{hash: crypto.SHA1, data: h.Digest[:]}}
+
+	return rawEvent{
+		typ:     EventType(h.Type),
+		data:    data,
+		index:   int(h.PCRIndex),
+		digests: digests,
+	}, nil
+}
+
+// TPM 2.0 event log format. See "5.2 Crypto Agile Log Entry Format"
+// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
+type rawEvent2Header struct {
+	PCRIndex uint32
+	Type     uint32
+}
+
+func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
+	var h rawEvent2Header
+
+	if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
+		return event, err
+	}
+	event.typ = EventType(h.Type)
+	event.index = int(h.PCRIndex)
+
+	// parse the event digests
+	var numDigests uint32
+	if err := binary.Read(r, binary.LittleEndian, &numDigests); err != nil {
+		return event, err
+	}
+
+	for i := 0; i < int(numDigests); i++ {
+		var algID uint16
+		if err := binary.Read(r, binary.LittleEndian, &algID); err != nil {
+			return event, err
+		}
+		var digest digest
+
+		for _, alg := range specID.algs {
+			if alg.ID != algID {
+				continue
+			}
+			if uint16(r.Len()) < alg.Size {
+				return event, fmt.Errorf("reading digest: %v", io.ErrUnexpectedEOF)
+			}
+			digest.data = make([]byte, alg.Size)
+			digest.hash = HashAlg(alg.ID).cryptoHash()
+		}
+		if len(digest.data) == 0 {
+			return event, fmt.Errorf("unknown algorithm ID %x", algID)
+		}
+		if _, err := io.ReadFull(r, digest.data); err != nil {
+			return event, err
+		}
+		event.digests = append(event.digests, digest)
+	}
+
+	// parse event data
+	var eventSize uint32
+	if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
+		return event, err
+	}
+	if eventSize == 0 {
+		return event, errors.New("event data size is 0")
+	}
+	if eventSize > uint32(r.Len()) {
+		return event, &eventSizeErr{eventSize, r.Len()}
+	}
+	event.data = make([]byte, int(eventSize))
+	if _, err := io.ReadFull(r, event.data); err != nil {
+		return event, err
+	}
+	return event, err
+}
diff --git a/metropolis/pkg/tpm/eventlog/internal/BUILD.bazel b/metropolis/pkg/tpm/eventlog/internal/BUILD.bazel
new file mode 100644
index 0000000..a73bcba
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/internal/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["events.go"],
+    importpath = "git.monogon.dev/source/nexantic.git/metropolis/pkg/tpm/eventlog/internal",
+    visibility = ["//metropolis/pkg/tpm/eventlog:__subpackages__"],
+    deps = [
+        "@com_github_google_certificate_transparency_go//asn1:go_default_library",
+        "@com_github_google_certificate_transparency_go//x509:go_default_library",
+    ],
+)
diff --git a/metropolis/pkg/tpm/eventlog/internal/events.go b/metropolis/pkg/tpm/eventlog/internal/events.go
new file mode 100644
index 0000000..d9b933b
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/internal/events.go
@@ -0,0 +1,403 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Taken from go-attestation under Apache 2.0
+package internal
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"unicode/utf16"
+
+	"github.com/google/certificate-transparency-go/asn1"
+	"github.com/google/certificate-transparency-go/x509"
+)
+
+const (
+	// maxNameLen is the maximum accepted byte length for a name field.
+	// This value should be larger than any reasonable value.
+	maxNameLen = 2048
+	// maxDataLen is the maximum size in bytes of a variable data field.
+	// This value should be larger than any reasonable value.
+	maxDataLen = 1024 * 1024 // 1 Megabyte.
+)
+
+// GUIDs representing the contents of an UEFI_SIGNATURE_LIST.
+var (
+	hashSHA256SigGUID        = efiGUID{0xc1c41626, 0x504c, 0x4092, [8]byte{0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28}}
+	hashSHA1SigGUID          = efiGUID{0x826ca512, 0xcf10, 0x4ac9, [8]byte{0xb1, 0x87, 0xbe, 0x01, 0x49, 0x66, 0x31, 0xbd}}
+	hashSHA224SigGUID        = efiGUID{0x0b6e5233, 0xa65c, 0x44c9, [8]byte{0x94, 0x07, 0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd}}
+	hashSHA384SigGUID        = efiGUID{0xff3e5307, 0x9fd0, 0x48c9, [8]byte{0x85, 0xf1, 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x01}}
+	hashSHA512SigGUID        = efiGUID{0x093e0fae, 0xa6c4, 0x4f50, [8]byte{0x9f, 0x1b, 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a}}
+	keyRSA2048SigGUID        = efiGUID{0x3c5766e8, 0x269c, 0x4e34, [8]byte{0xaa, 0x14, 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6}}
+	certRSA2048SHA256SigGUID = efiGUID{0xe2b36190, 0x879b, 0x4a3d, [8]byte{0xad, 0x8d, 0xf2, 0xe7, 0xbb, 0xa3, 0x27, 0x84}}
+	certRSA2048SHA1SigGUID   = efiGUID{0x67f8444f, 0x8743, 0x48f1, [8]byte{0xa3, 0x28, 0x1e, 0xaa, 0xb8, 0x73, 0x60, 0x80}}
+	certX509SigGUID          = efiGUID{0xa5c059a1, 0x94e4, 0x4aa7, [8]byte{0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72}}
+	certHashSHA256SigGUID    = efiGUID{0x3bd2a492, 0x96c0, 0x4079, [8]byte{0xb4, 0x20, 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed}}
+	certHashSHA384SigGUID    = efiGUID{0x7076876e, 0x80c2, 0x4ee6, [8]byte{0xaa, 0xd2, 0x28, 0xb3, 0x49, 0xa6, 0x86, 0x5b}}
+	certHashSHA512SigGUID    = efiGUID{0x446dbf63, 0x2502, 0x4cda, [8]byte{0xbc, 0xfa, 0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d}}
+)
+
+// EventType describes the type of event signalled in the event log.
+type EventType uint32
+
+// 	BIOS Events (TCG PC Client Specific Implementation Specification for Conventional BIOS 1.21)
+const (
+	PrebootCert          EventType = 0x00000000
+	PostCode             EventType = 0x00000001
+	unused               EventType = 0x00000002
+	NoAction             EventType = 0x00000003
+	Separator            EventType = 0x00000004
+	Action               EventType = 0x00000005
+	EventTag             EventType = 0x00000006
+	SCRTMContents        EventType = 0x00000007
+	SCRTMVersion         EventType = 0x00000008
+	CpuMicrocode         EventType = 0x00000009
+	PlatformConfigFlags  EventType = 0x0000000A
+	TableOfDevices       EventType = 0x0000000B
+	CompactHash          EventType = 0x0000000C
+	Ipl                  EventType = 0x0000000D
+	IplPartitionData     EventType = 0x0000000E
+	NonhostCode          EventType = 0x0000000F
+	NonhostConfig        EventType = 0x00000010
+	NonhostInfo          EventType = 0x00000011
+	OmitBootDeviceEvents EventType = 0x00000012
+)
+
+// EFI Events (TCG EFI Platform Specification Version 1.22)
+const (
+	EFIEventBase               EventType = 0x80000000
+	EFIVariableDriverConfig    EventType = 0x80000001
+	EFIVariableBoot            EventType = 0x80000002
+	EFIBootServicesApplication EventType = 0x80000003
+	EFIBootServicesDriver      EventType = 0x80000004
+	EFIRuntimeServicesDriver   EventType = 0x80000005
+	EFIGPTEvent                EventType = 0x80000006
+	EFIAction                  EventType = 0x80000007
+	EFIPlatformFirmwareBlob    EventType = 0x80000008
+	EFIHandoffTables           EventType = 0x80000009
+	EFIHCRTMEvent              EventType = 0x80000010
+	EFIVariableAuthority       EventType = 0x800000e0
+)
+
+// ErrSigMissingGUID is returned if an EFI_SIGNATURE_DATA structure was parsed
+// successfully, however was missing the SignatureOwner GUID. This case is
+// handled specially as a workaround for a bug relating to authority events.
+var ErrSigMissingGUID = errors.New("signature data was missing owner GUID")
+
+var eventTypeNames = map[EventType]string{
+	PrebootCert:          "Preboot Cert",
+	PostCode:             "POST Code",
+	unused:               "Unused",
+	NoAction:             "No Action",
+	Separator:            "Separator",
+	Action:               "Action",
+	EventTag:             "Event Tag",
+	SCRTMContents:        "S-CRTM Contents",
+	SCRTMVersion:         "S-CRTM Version",
+	CpuMicrocode:         "CPU Microcode",
+	PlatformConfigFlags:  "Platform Config Flags",
+	TableOfDevices:       "Table of Devices",
+	CompactHash:          "Compact Hash",
+	Ipl:                  "IPL",
+	IplPartitionData:     "IPL Partition Data",
+	NonhostCode:          "Non-Host Code",
+	NonhostConfig:        "Non-HostConfig",
+	NonhostInfo:          "Non-Host Info",
+	OmitBootDeviceEvents: "Omit Boot Device Events",
+
+	EFIEventBase:               "EFI Event Base",
+	EFIVariableDriverConfig:    "EFI Variable Driver Config",
+	EFIVariableBoot:            "EFI Variable Boot",
+	EFIBootServicesApplication: "EFI Boot Services Application",
+	EFIBootServicesDriver:      "EFI Boot Services Driver",
+	EFIRuntimeServicesDriver:   "EFI Runtime Services Driver",
+	EFIGPTEvent:                "EFI GPT Event",
+	EFIAction:                  "EFI Action",
+	EFIPlatformFirmwareBlob:    "EFI Platform Firmware Blob",
+	EFIVariableAuthority:       "EFI Variable Authority",
+	EFIHandoffTables:           "EFI Handoff Tables",
+	EFIHCRTMEvent:              "EFI H-CRTM Event",
+}
+
+func (e EventType) String() string {
+	if s, ok := eventTypeNames[e]; ok {
+		return s
+	}
+	return fmt.Sprintf("EventType(0x%x)", uint32(e))
+}
+
+// UntrustedParseEventType returns the event type indicated by
+// the provided value.
+func UntrustedParseEventType(et uint32) (EventType, error) {
+	// "The value associated with a UEFI specific platform event type MUST be in
+	// the range between 0x80000000 and 0x800000FF, inclusive."
+	if (et < 0x80000000 && et > 0x800000FF) || (et < 0x0 && et > 0x12) {
+		return EventType(0), fmt.Errorf("event type not between [0x0, 0x12] or [0x80000000, 0x800000FF]: got %#x", et)
+	}
+	if _, ok := eventTypeNames[EventType(et)]; !ok {
+		return EventType(0), fmt.Errorf("unknown event type %#x", et)
+	}
+	return EventType(et), nil
+}
+
+// efiGUID represents the EFI_GUID type.
+// See section "2.3.1 Data Types" in the specification for more information.
+// type efiGUID [16]byte
+type efiGUID struct {
+	Data1 uint32
+	Data2 uint16
+	Data3 uint16
+	Data4 [8]byte
+}
+
+func (d efiGUID) String() string {
+	var u [8]byte
+	binary.BigEndian.PutUint32(u[:4], d.Data1)
+	binary.BigEndian.PutUint16(u[4:6], d.Data2)
+	binary.BigEndian.PutUint16(u[6:8], d.Data3)
+	return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], d.Data4[:2], d.Data4[2:])
+}
+
+// UEFIVariableDataHeader represents the leading fixed-size fields
+// within UEFI_VARIABLE_DATA.
+type UEFIVariableDataHeader struct {
+	VariableName       efiGUID
+	UnicodeNameLength  uint64 // uintN
+	VariableDataLength uint64 // uintN
+}
+
+// UEFIVariableData represents the UEFI_VARIABLE_DATA structure.
+type UEFIVariableData struct {
+	Header       UEFIVariableDataHeader
+	UnicodeName  []uint16
+	VariableData []byte // []int8
+}
+
+// ParseUEFIVariableData parses the data section of an event structured as
+// a UEFI variable.
+//
+// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100
+func ParseUEFIVariableData(r io.Reader) (ret UEFIVariableData, err error) {
+	err = binary.Read(r, binary.LittleEndian, &ret.Header)
+	if err != nil {
+		return
+	}
+	if ret.Header.UnicodeNameLength > maxNameLen {
+		return UEFIVariableData{}, fmt.Errorf("unicode name too long: %d > %d", ret.Header.UnicodeNameLength, maxNameLen)
+	}
+	ret.UnicodeName = make([]uint16, ret.Header.UnicodeNameLength)
+	for i := 0; uint64(i) < ret.Header.UnicodeNameLength; i++ {
+		err = binary.Read(r, binary.LittleEndian, &ret.UnicodeName[i])
+		if err != nil {
+			return
+		}
+	}
+	if ret.Header.VariableDataLength > maxDataLen {
+		return UEFIVariableData{}, fmt.Errorf("variable data too long: %d > %d", ret.Header.VariableDataLength, maxDataLen)
+	}
+	ret.VariableData = make([]byte, ret.Header.VariableDataLength)
+	_, err = io.ReadFull(r, ret.VariableData)
+	return
+}
+
+func (v *UEFIVariableData) VarName() string {
+	return string(utf16.Decode(v.UnicodeName))
+}
+
+func (v *UEFIVariableData) SignatureData() (certs []x509.Certificate, hashes [][]byte, err error) {
+	return parseEfiSignatureList(v.VariableData)
+}
+
+// UEFIVariableAuthority describes the contents of a UEFI variable authority
+// event.
+type UEFIVariableAuthority struct {
+	Certs []x509.Certificate
+}
+
+// ParseUEFIVariableAuthority parses the data section of an event structured as
+// a UEFI variable authority.
+//
+// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789
+func ParseUEFIVariableAuthority(r io.Reader) (UEFIVariableAuthority, error) {
+	v, err := ParseUEFIVariableData(r)
+	if err != nil {
+		return UEFIVariableAuthority{}, err
+	}
+	certs, err := parseEfiSignature(v.VariableData)
+	return UEFIVariableAuthority{Certs: certs}, err
+}
+
+// efiSignatureData represents the EFI_SIGNATURE_DATA type.
+// See section "31.4.1 Signature Database" in the specification for more information.
+type efiSignatureData struct {
+	SignatureOwner efiGUID
+	SignatureData  []byte // []int8
+}
+
+// efiSignatureList represents the EFI_SIGNATURE_LIST type.
+// See section "31.4.1 Signature Database" in the specification for more information.
+type efiSignatureListHeader struct {
+	SignatureType       efiGUID
+	SignatureListSize   uint32
+	SignatureHeaderSize uint32
+	SignatureSize       uint32
+}
+
+type efiSignatureList struct {
+	Header        efiSignatureListHeader
+	SignatureData []byte
+	Signatures    []byte
+}
+
+// parseEfiSignatureList parses a EFI_SIGNATURE_LIST structure.
+// The structure and related GUIDs are defined at:
+// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1790
+func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
+	if len(b) < 28 {
+		// Being passed an empty signature list here appears to be valid
+		return nil, nil, nil
+	}
+	signatures := efiSignatureList{}
+	buf := bytes.NewReader(b)
+	certificates := []x509.Certificate{}
+	hashes := [][]byte{}
+
+	for buf.Len() > 0 {
+		err := binary.Read(buf, binary.LittleEndian, &signatures.Header)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		if signatures.Header.SignatureHeaderSize > maxDataLen {
+			return nil, nil, fmt.Errorf("signature header too large: %d > %d", signatures.Header.SignatureHeaderSize, maxDataLen)
+		}
+		if signatures.Header.SignatureListSize > maxDataLen {
+			return nil, nil, fmt.Errorf("signature list too large: %d > %d", signatures.Header.SignatureListSize, maxDataLen)
+		}
+
+		signatureType := signatures.Header.SignatureType
+		switch signatureType {
+		case certX509SigGUID: // X509 certificate
+			for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
+				signature := efiSignatureData{}
+				signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
+				err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
+				if err != nil {
+					return nil, nil, err
+				}
+				err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
+				if err != nil {
+					return nil, nil, err
+				}
+				cert, err := x509.ParseCertificate(signature.SignatureData)
+				if err != nil {
+					return nil, nil, err
+				}
+				sigOffset += int(signatures.Header.SignatureSize)
+				certificates = append(certificates, *cert)
+			}
+		case hashSHA256SigGUID: // SHA256
+			for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
+				signature := efiSignatureData{}
+				signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
+				err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
+				if err != nil {
+					return nil, nil, err
+				}
+				err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
+				if err != nil {
+					return nil, nil, err
+				}
+				hashes = append(hashes, signature.SignatureData)
+				sigOffset += int(signatures.Header.SignatureSize)
+			}
+		case keyRSA2048SigGUID:
+			err = errors.New("unhandled RSA2048 key")
+		case certRSA2048SHA256SigGUID:
+			err = errors.New("unhandled RSA2048-SHA256 key")
+		case hashSHA1SigGUID:
+			err = errors.New("unhandled SHA1 hash")
+		case certRSA2048SHA1SigGUID:
+			err = errors.New("unhandled RSA2048-SHA1 key")
+		case hashSHA224SigGUID:
+			err = errors.New("unhandled SHA224 hash")
+		case hashSHA384SigGUID:
+			err = errors.New("unhandled SHA384 hash")
+		case hashSHA512SigGUID:
+			err = errors.New("unhandled SHA512 hash")
+		case certHashSHA256SigGUID:
+			err = errors.New("unhandled X509-SHA256 hash metadata")
+		case certHashSHA384SigGUID:
+			err = errors.New("unhandled X509-SHA384 hash metadata")
+		case certHashSHA512SigGUID:
+			err = errors.New("unhandled X509-SHA512 hash metadata")
+		default:
+			err = fmt.Errorf("unhandled signature type %s", signatureType)
+		}
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+	return certificates, hashes, nil
+}
+
+// EFISignatureData represents the EFI_SIGNATURE_DATA type.
+// See section "31.4.1 Signature Database" in the specification
+// for more information.
+type EFISignatureData struct {
+	SignatureOwner efiGUID
+	SignatureData  []byte // []int8
+}
+
+func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
+	certificates := []x509.Certificate{}
+
+	if len(b) < 16 {
+		return nil, fmt.Errorf("invalid signature: buffer smaller than header (%d < %d)", len(b), 16)
+	}
+
+	buf := bytes.NewReader(b)
+	signature := EFISignatureData{}
+	signature.SignatureData = make([]byte, len(b)-16)
+
+	if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner); err != nil {
+		return certificates, err
+	}
+	if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureData); err != nil {
+		return certificates, err
+	}
+
+	cert, err := x509.ParseCertificate(signature.SignatureData)
+	if err == nil {
+		certificates = append(certificates, *cert)
+	} else {
+		// A bug in shim may cause an event to be missing the SignatureOwner GUID.
+		// We handle this, but signal back to the caller using ErrSigMissingGUID.
+		if _, isStructuralErr := err.(asn1.StructuralError); isStructuralErr {
+			var err2 error
+			cert, err2 = x509.ParseCertificate(b)
+			if err2 == nil {
+				certificates = append(certificates, *cert)
+				err = ErrSigMissingGUID
+			}
+		}
+	}
+	return certificates, err
+}
diff --git a/metropolis/pkg/tpm/eventlog/secureboot.go b/metropolis/pkg/tpm/eventlog/secureboot.go
new file mode 100644
index 0000000..46e1f95
--- /dev/null
+++ b/metropolis/pkg/tpm/eventlog/secureboot.go
@@ -0,0 +1,210 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Taken and pruned from go-attestation under Apache 2.0
+package eventlog
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+
+	"github.com/google/certificate-transparency-go/x509"
+
+	"git.monogon.dev/source/nexantic.git/metropolis/pkg/tpm/eventlog/internal"
+)
+
+// SecurebootState describes the secure boot status of a machine, as determined
+// by processing its event log.
+type SecurebootState struct {
+	Enabled bool
+
+	// PlatformKeys enumerates keys which can sign a key exchange key.
+	PlatformKeys []x509.Certificate
+	// PlatformKeys enumerates key hashes which can sign a key exchange key.
+	PlatformKeyHashes [][]byte
+
+	// ExchangeKeys enumerates keys which can sign a database of permitted or
+	// forbidden keys.
+	ExchangeKeys []x509.Certificate
+	// ExchangeKeyHashes enumerates key hashes which can sign a database or
+	// permitted or forbidden keys.
+	ExchangeKeyHashes [][]byte
+
+	// PermittedKeys enumerates keys which may sign binaries to run.
+	PermittedKeys []x509.Certificate
+	// PermittedHashes enumerates hashes which permit binaries to run.
+	PermittedHashes [][]byte
+
+	// ForbiddenKeys enumerates keys which must not permit a binary to run.
+	ForbiddenKeys []x509.Certificate
+	// ForbiddenKeys enumerates hashes which must not permit a binary to run.
+	ForbiddenHashes [][]byte
+
+	// PreSeparatorAuthority describes the use of a secure-boot key to authorize
+	// the execution of a binary before the separator.
+	PreSeparatorAuthority []x509.Certificate
+	// PostSeparatorAuthority describes the use of a secure-boot key to authorize
+	// the execution of a binary after the separator.
+	PostSeparatorAuthority []x509.Certificate
+}
+
+// ParseSecurebootState parses a series of events to determine the
+// configuration of secure boot on a device. An error is returned if
+// the state cannot be determined, or if the event log is structured
+// in such a way that it may have been tampered post-execution of
+// platform firmware.
+func ParseSecurebootState(events []Event) (*SecurebootState, error) {
+	// This algorithm verifies the following:
+	// - All events in PCR 7 have event types which are expected in PCR 7.
+	// - All events are parsable according to their event type.
+	// - All events have digests values corresponding to their data/event type.
+	// - No unverifiable events were present.
+	// - All variables are specified before the separator and never duplicated.
+	// - The SecureBoot variable has a value of 0 or 1.
+	// - If SecureBoot was 1 (enabled), authority events were present indicating
+	//   keys were used to perform verification.
+	// - If SecureBoot was 1 (enabled), platform + exchange + database keys
+	//   were specified.
+	// - No UEFI debugger was attached.
+
+	var (
+		out           SecurebootState
+		seenSeparator bool
+		seenAuthority bool
+		seenVars      = map[string]bool{}
+	)
+
+	for _, e := range events {
+		if e.Index != 7 {
+			continue
+		}
+
+		et, err := internal.UntrustedParseEventType(uint32(e.Type))
+		if err != nil {
+			return nil, fmt.Errorf("unrecognised event type: %v", err)
+		}
+
+		digestVerify := e.digestEquals(e.Data)
+		switch et {
+		case internal.Separator:
+			if seenSeparator {
+				return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
+			}
+			seenSeparator = true
+			if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
+				return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
+			}
+			if digestVerify != nil {
+				return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
+			}
+
+		case internal.EFIAction:
+			if string(e.Data) == "UEFI Debug Mode" {
+				return nil, errors.New("a UEFI debugger was present during boot")
+			}
+			return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
+
+		case internal.EFIVariableDriverConfig:
+			v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
+			if err != nil {
+				return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
+			}
+			if _, seenBefore := seenVars[v.VarName()]; seenBefore {
+				return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
+			}
+			seenVars[v.VarName()] = true
+			if seenSeparator {
+				return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
+			}
+
+			if digestVerify != nil {
+				return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
+			}
+
+			switch v.VarName() {
+			case "SecureBoot":
+				if len(v.VariableData) != 1 {
+					return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
+				}
+				out.Enabled = v.VariableData[0] == 1
+			case "PK":
+				if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
+					return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
+				}
+			case "KEK":
+				if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
+					return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
+				}
+			case "db":
+				if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
+					return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
+				}
+			case "dbx":
+				if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
+					return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
+				}
+			}
+
+		case internal.EFIVariableAuthority:
+			a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data))
+			if err != nil {
+				// Workaround for: https://github.com/google/go-attestation/issues/157
+				if err == internal.ErrSigMissingGUID {
+					// Versions of shim which do not carry
+					// https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50
+					// have an erroneous additional byte in the event, which breaks digest
+					// verification. If verification failed, we try removing the last byte.
+					if digestVerify != nil {
+						digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
+					}
+				} else {
+					return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
+				}
+			}
+			seenAuthority = true
+			if digestVerify != nil {
+				return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
+			}
+			if !seenSeparator {
+				out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
+			} else {
+				out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
+			}
+
+		default:
+			return nil, fmt.Errorf("unexpected event type: %v", et)
+		}
+	}
+
+	if !out.Enabled {
+		return &out, nil
+	}
+
+	if !seenAuthority {
+		return nil, errors.New("secure boot was enabled but no key was used")
+	}
+	if len(out.PlatformKeys) == 0 && len(out.PlatformKeyHashes) == 0 {
+		return nil, errors.New("secure boot was enabled but no platform keys were known")
+	}
+	if len(out.ExchangeKeys) == 0 && len(out.ExchangeKeyHashes) == 0 {
+		return nil, errors.New("secure boot was enabled but no key exchange keys were known")
+	}
+	if len(out.PermittedKeys) == 0 && len(out.PermittedHashes) == 0 {
+		return nil, errors.New("secure boot was enabled but no keys or hashes were permitted")
+	}
+	return &out, nil
+}