go-attestation/attest/eventlog.go

833 lines
24 KiB
Go
Raw Normal View History

// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
package attest
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"sort"
"strings"
// Ensure hashes are available.
_ "crypto/sha256"
2023-06-13 14:43:38 +00:00
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
)
// ReplayError describes the parsed events that failed to verify against
// a particular PCR.
type ReplayError struct {
Events []Event
// InvalidPCRs reports the set of PCRs where the event log replay failed.
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)
}
// EventType indicates what kind of data an event is reporting.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=103
type EventType uint32
var eventTypeStrings = map[uint32]string{
0x00000000: "EV_PREBOOT_CERT",
0x00000001: "EV_POST_CODE",
0x00000002: "EV_UNUSED",
0x00000003: "EV_NO_ACTION",
0x00000004: "EV_SEPARATOR",
0x00000005: "EV_ACTION",
0x00000006: "EV_EVENT_TAG",
0x00000007: "EV_S_CRTM_CONTENTS",
0x00000008: "EV_S_CRTM_VERSION",
0x00000009: "EV_CPU_MICROCODE",
0x0000000A: "EV_PLATFORM_CONFIG_FLAGS",
0x0000000B: "EV_TABLE_OF_DEVICES",
0x0000000C: "EV_COMPACT_HASH",
0x0000000D: "EV_IPL",
0x0000000E: "EV_IPL_PARTITION_DATA",
0x0000000F: "EV_NONHOST_CODE",
0x00000010: "EV_NONHOST_CONFIG",
0x00000011: "EV_NONHOST_INFO",
0x00000012: "EV_OMIT_BOOT_DEVICE_EVENTS",
0x80000000: "EV_EFI_EVENT_BASE",
0x80000001: "EV_EFI_VARIABLE_DRIVER_CONFIG",
0x80000002: "EV_EFI_VARIABLE_BOOT",
0x80000003: "EV_EFI_BOOT_SERVICES_APPLICATION",
0x80000004: "EV_EFI_BOOT_SERVICES_DRIVER",
0x80000005: "EV_EFI_RUNTIME_SERVICES_DRIVER",
0x80000006: "EV_EFI_GPT_EVENT",
0x80000007: "EV_EFI_ACTION",
0x80000008: "EV_EFI_PLATFORM_FIRMWARE_BLOB",
0x80000009: "EV_EFI_HANDOFF_TABLES",
0x80000010: "EV_EFI_HCRTM_EVENT",
0x800000E0: "EV_EFI_VARIABLE_AUTHORITY",
}
// String returns the Spec name of the EventType, for example "EV_ACTION". If
// unknown, it returns a formatted string of the EventType value.
func (e EventType) String() string {
if s, ok := eventTypeStrings[uint32(e)]; ok {
return s
}
// NOTE: 0x00000013-0x0000FFFF are reserverd. Should we include that
// information in the formatting?
return fmt.Sprintf("EventType(0x%08x)", uint32(e))
}
// Event is a single event from a TCG event log. This reports descrete items such
// as BIOS measurements or EFI states.
//
// There are many pitfalls for using event log events correctly to determine the
// state of a machine[1]. In general it's much safer to only rely on the raw PCR
// values and use the event log for debugging.
//
// [1] https://github.com/google/go-attestation/blob/master/docs/event-log-disclosure.md
type Event struct {
// order of the event in the event log.
sequence int
// Index of the PCR that this event was replayed against.
Index int
// Untrusted type of the event. This value is not verified by event log replays
// and can be tampered with. It should NOT be used without additional context,
// and unrecognized event types should result in errors.
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
specIDEvent *specIDEvent
}
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)
if e.specIDEvent != nil {
dupe := *e.specIDEvent
out.specIDEvent = &dupe
}
return &out
}
// Events returns events that have not been replayed against the PCR values and
// are therefore unverified. The returned events contain the digest that matches
// the provided hash algorithm, or are empty if that event didn't contain a
// digest for that hash.
//
// This method is insecure and should only be used for debugging.
func (e *EventLog) Events(hash HashAlg) []Event {
var events []Event
for _, re := range e.rawEvents {
ev := Event{
Index: re.index,
Type: re.typ,
Data: re.data,
}
for _, digest := range re.digests {
if hash.cryptoHash() != digest.hash {
continue
}
ev.Digest = digest.data
break
}
events = append(events, ev)
}
return events
}
// Verify replays the event log against a TPM's PCR values, returning the
// events which could be matched to a provided PCR value.
//
// PCRs provide no security guarantees unless they're attested to have been
// generated by a TPM. Verify does not perform these checks.
//
// 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
}
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
}
type rawAttestationData struct {
Version [4]byte // This MUST be 1.1.0.0
Fixed [4]byte // This SHALL always be the string QUOT
Digest [20]byte // PCR Composite Hash
Nonce [20]byte // Nonce Hash
}
var (
fixedQuote = [4]byte{'Q', 'U', 'O', 'T'}
)
type rawPCRComposite struct {
Size uint16 // always 3
PCRMask [3]byte
Values tpmutil.U32Bytes
}
func (a *AKPublic) validate12Quote(quote Quote, pcrs []PCR, nonce []byte) error {
pub, ok := a.Public.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("unsupported public key type: %T", a.Public)
}
qHash := sha1.Sum(quote.Quote)
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA1, qHash[:], quote.Signature); err != nil {
return fmt.Errorf("invalid quote signature: %v", err)
}
var att rawAttestationData
if _, err := tpmutil.Unpack(quote.Quote, &att); err != nil {
return fmt.Errorf("parsing quote: %v", err)
}
// TODO(ericchiang): validate Version field.
if att.Nonce != sha1.Sum(nonce) {
return fmt.Errorf("invalid nonce")
}
if att.Fixed != fixedQuote {
return fmt.Errorf("quote wasn't a QUOT object: %x", att.Fixed)
}
// See 5.4.1 Creating a PCR composite hash
sort.Slice(pcrs, func(i, j int) bool { return pcrs[i].Index < pcrs[j].Index })
var (
pcrMask [3]byte // bitmap indicating which PCRs are active
values []byte // appended values of all PCRs
)
for _, pcr := range pcrs {
if pcr.Index < 0 || pcr.Index >= 24 {
return fmt.Errorf("invalid PCR index: %d", pcr.Index)
}
pcrMask[pcr.Index/8] |= 1 << uint(pcr.Index%8)
values = append(values, pcr.Digest...)
}
composite, err := tpmutil.Pack(rawPCRComposite{3, pcrMask, values})
if err != nil {
return fmt.Errorf("marshaling PCRs: %v", err)
}
if att.Digest != sha1.Sum(composite) {
return fmt.Errorf("PCRs passed didn't match quote: %v", err)
}
// All provided PCRs are used to construct the composite hash which
// is verified against the quote (for TPM 1.2), so if we got this far,
// all PCR values are verified.
for i := range pcrs {
pcrs[i].quoteVerified = true
}
return nil
}
func (a *AKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error {
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(quote.Signature))
if err != nil {
return fmt.Errorf("parse quote signature: %v", err)
}
sigHash := a.Hash.New()
sigHash.Write(quote.Quote)
switch pub := a.Public.(type) {
case *rsa.PublicKey:
if sig.RSA == nil {
return fmt.Errorf("rsa public key provided for ec signature")
}
sigBytes := []byte(sig.RSA.Signature)
if err := rsa.VerifyPKCS1v15(pub, a.Hash, sigHash.Sum(nil), sigBytes); err != nil {
return fmt.Errorf("invalid quote signature: %v", err)
}
default:
// TODO(ericchiang): support ecdsa
return fmt.Errorf("unsupported public key type %T", pub)
}
att, err := tpm2.DecodeAttestationData(quote.Quote)
if err != nil {
return fmt.Errorf("parsing quote signature: %v", err)
}
if att.Type != tpm2.TagAttestQuote {
return fmt.Errorf("attestation isn't a quote, tag of type 0x%x", att.Type)
}
if !bytes.Equal([]byte(att.ExtraData), nonce) {
return fmt.Errorf("nonce = %#v, want %#v", []byte(att.ExtraData), nonce)
}
pcrByIndex := map[int][]byte{}
pcrDigestAlg := HashAlg(att.AttestedQuoteInfo.PCRSelection.Hash).cryptoHash()
for _, pcr := range pcrs {
if pcr.DigestAlg == pcrDigestAlg {
pcrByIndex[pcr.Index] = pcr.Digest
}
}
sigHash.Reset()
quotePCRs := make(map[int]struct{}, len(att.AttestedQuoteInfo.PCRSelection.PCRs))
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
digest, ok := pcrByIndex[index]
if !ok {
return fmt.Errorf("quote was over PCR %d which wasn't provided", index)
}
quotePCRs[index] = struct{}{}
sigHash.Write(digest)
}
for index := range pcrByIndex {
if _, exists := quotePCRs[index]; !exists {
return fmt.Errorf("provided PCR %d was not included in quote", index)
}
}
if !bytes.Equal(sigHash.Sum(nil), att.AttestedQuoteInfo.PCRDigest) {
return fmt.Errorf("quote digest didn't match pcrs provided")
}
// If we got this far, all included PCRs with a digest algorithm matching that
// of the quote are verified. As such, we set their quoteVerified bit.
for i, pcr := range pcrs {
if _, exists := quotePCRs[pcr.Index]; exists && pcr.DigestAlg == pcrDigestAlg {
pcrs[i].quoteVerified = true
}
}
return nil
}
func extend(pcr PCR, replay []byte, e rawEvent, locality byte) (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())
b[h.Size()-1] = locality
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 possess an event digest with the specified algorithm.
func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
var (
replay []byte
outEvents []Event
locality byte
)
for _, e := range rawEvents {
if e.index != pcr.Index {
continue
}
// If TXT is enabled then the first event for PCR0
// should be a StartupLocality event. The final byte
// of this event indicates the locality from which
// TPM2_Startup() was issued. The initial value of
// PCR0 is equal to the locality.
if e.typ == eventTypeNoAction {
if pcr.Index == 0 && len(e.data) == 17 && strings.HasPrefix(string(e.data), "StartupLocality") {
locality = e.data[len(e.data)-1]
}
continue
}
replayValue, digest, err := extend(pcr, replay, e, locality)
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 && len(e.data) >= binary.Size(specIDEventHeader{}) {
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 guarantee that events have SHA256
// digests.
parseFn = parseRawEvent2
el.specIDEvent = specID
} 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
)
type specIDEventHeader struct {
Signature [16]byte
PlatformClass uint32
VersionMinor uint8
VersionMajor uint8
Errata uint8
UintnSize uint8
NumAlgs uint32
}
// 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 specIDEventHeader
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, fmt.Errorf("reading event header: %w: %X", err, b)
}
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, fmt.Errorf("header deserialization error: %w", err)
}
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, fmt.Errorf("reading data error: %w", 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 r.Len() < int(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 > 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
}
// AppendEvents takes a series of TPM 2.0 event logs and combines
// them into a single sequence of events with a single header.
//
// Additional logs must not use a digest algorithm which was not
// present in the original log.
func AppendEvents(base []byte, additional ...[]byte) ([]byte, error) {
baseLog, err := ParseEventLog(base)
if err != nil {
return nil, fmt.Errorf("base: %v", err)
}
if baseLog.specIDEvent == nil {
return nil, errors.New("tpm 1.2 event logs cannot be combined")
}
outBuff := make([]byte, len(base))
copy(outBuff, base)
out := bytes.NewBuffer(outBuff)
for i, l := range additional {
log, err := ParseEventLog(l)
if err != nil {
return nil, fmt.Errorf("log %d: %v", i, err)
}
if log.specIDEvent == nil {
return nil, fmt.Errorf("log %d: cannot use tpm 1.2 event log as a source", i)
}
algCheck:
for _, alg := range log.specIDEvent.algs {
for _, baseAlg := range baseLog.specIDEvent.algs {
if baseAlg == alg {
continue algCheck
}
}
return nil, fmt.Errorf("log %d: cannot use digest (%+v) not present in base log", i, alg)
}
for x, e := range log.rawEvents {
// Serialize header (PCR index, event type, number of digests)
binary.Write(out, binary.LittleEndian, rawEvent2Header{
PCRIndex: uint32(e.index),
Type: uint32(e.typ),
})
binary.Write(out, binary.LittleEndian, uint32(len(e.digests)))
// Serialize digests
for _, d := range e.digests {
var algID uint16
switch d.hash {
case crypto.SHA256:
algID = uint16(HashSHA256)
case crypto.SHA1:
algID = uint16(HashSHA1)
default:
return nil, fmt.Errorf("log %d: event %d: unhandled hash function %v", i, x, d.hash)
}
binary.Write(out, binary.LittleEndian, algID)
out.Write(d.data)
}
// Serialize event data
binary.Write(out, binary.LittleEndian, uint32(len(e.data)))
out.Write(e.data)
}
}
return out.Bytes(), nil
}