blob: 39d076bdc23294d7f3f81c322dcce45c61a9d26e [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Bruna50e8452020-09-09 17:09:27 +02002// SPDX-License-Identifier: Apache-2.0
Lorenz Bruna50e8452020-09-09 17:09:27 +02003
Serge Bazanski216fe7b2021-05-21 18:36:16 +02004// Taken and pruned from go-attestation revision
5// 2453c8f39a4ff46009f6a9db6fb7c6cca789d9a1 under Apache 2.0
Lorenz Bruna50e8452020-09-09 17:09:27 +02006
7package eventlog
8
9import (
10 "bytes"
11 "crypto"
12 "crypto/sha1"
13 "crypto/sha256"
14 "encoding/binary"
15 "errors"
16 "fmt"
17 "io"
18 "sort"
19
Lorenz Bruna50e8452020-09-09 17:09:27 +020020 "github.com/google/go-tpm/tpm2"
21)
22
23// HashAlg identifies a hashing Algorithm.
24type HashAlg uint8
25
26// Valid hash algorithms.
27var (
28 HashSHA1 = HashAlg(tpm2.AlgSHA1)
29 HashSHA256 = HashAlg(tpm2.AlgSHA256)
30)
31
32func (a HashAlg) cryptoHash() crypto.Hash {
33 switch a {
34 case HashSHA1:
35 return crypto.SHA1
36 case HashSHA256:
37 return crypto.SHA256
38 }
39 return 0
40}
41
42func (a HashAlg) goTPMAlg() tpm2.Algorithm {
43 switch a {
44 case HashSHA1:
45 return tpm2.AlgSHA1
46 case HashSHA256:
47 return tpm2.AlgSHA256
48 }
49 return 0
50}
51
52// String returns a human-friendly representation of the hash algorithm.
53func (a HashAlg) String() string {
54 switch a {
55 case HashSHA1:
56 return "SHA1"
57 case HashSHA256:
58 return "SHA256"
59 }
60 return fmt.Sprintf("HashAlg<%d>", int(a))
61}
62
63// ReplayError describes the parsed events that failed to verify against
64// a particular PCR.
65type ReplayError struct {
66 Events []Event
67 invalidPCRs []int
68}
69
70func (e ReplayError) affected(pcr int) bool {
71 for _, p := range e.invalidPCRs {
72 if p == pcr {
73 return true
74 }
75 }
76 return false
77}
78
79// Error returns a human-friendly description of replay failures.
80func (e ReplayError) Error() string {
81 return fmt.Sprintf("event log failed to verify: the following registers failed to replay: %v", e.invalidPCRs)
82}
83
84// TPM algorithms. See the TPM 2.0 specification section 6.3.
85//
Tim Windelschmidt99e15112025-02-05 17:38:16 +010086// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf#page=42
Lorenz Bruna50e8452020-09-09 17:09:27 +020087const (
88 algSHA1 uint16 = 0x0004
89 algSHA256 uint16 = 0x000B
90)
91
92// EventType indicates what kind of data an event is reporting.
93type EventType uint32
94
95// Event is a single event from a TCG event log. This reports descrete items such
96// as BIOs measurements or EFI states.
97type Event struct {
98 // order of the event in the event log.
99 sequence int
100
101 // PCR index of the event.
102 Index int
103 // Type of the event.
104 Type EventType
105
106 // Data of the event. For certain kinds of events, this must match the event
107 // digest to be valid.
108 Data []byte
109 // Digest is the verified digest of the event data. While an event can have
110 // multiple for different hash values, this is the one that was matched to the
111 // PCR value.
112 Digest []byte
113
114 // TODO(ericchiang): Provide examples or links for which event types must
115 // match their data to their digest.
116}
117
118func (e *Event) digestEquals(b []byte) error {
119 if len(e.Digest) == 0 {
120 return errors.New("no digests present")
121 }
122
123 switch len(e.Digest) {
124 case crypto.SHA256.Size():
125 s := sha256.Sum256(b)
126 if bytes.Equal(s[:], e.Digest) {
127 return nil
128 }
129 case crypto.SHA1.Size():
130 s := sha1.Sum(b)
131 if bytes.Equal(s[:], e.Digest) {
132 return nil
133 }
134 default:
135 return fmt.Errorf("cannot compare hash of length %d", len(e.Digest))
136 }
137
138 return fmt.Errorf("digest (len %d) does not match", len(e.Digest))
139}
140
141// EventLog is a parsed measurement log. This contains unverified data representing
142// boot events that must be replayed against PCR values to determine authenticity.
143type EventLog struct {
144 // Algs holds the set of algorithms that the event log uses.
145 Algs []HashAlg
146
147 rawEvents []rawEvent
148}
149
150func (e *EventLog) clone() *EventLog {
151 out := EventLog{
152 Algs: make([]HashAlg, len(e.Algs)),
153 rawEvents: make([]rawEvent, len(e.rawEvents)),
154 }
155 copy(out.Algs, e.Algs)
156 copy(out.rawEvents, e.rawEvents)
157 return &out
158}
159
160type elWorkaround struct {
161 id string
162 affectedPCR int
163 apply func(e *EventLog) error
164}
165
166// inject3 appends two new events into the event log.
167func inject3(e *EventLog, pcr int, data1, data2, data3 string) error {
168 if err := inject(e, pcr, data1); err != nil {
169 return err
170 }
171 if err := inject(e, pcr, data2); err != nil {
172 return err
173 }
174 return inject(e, pcr, data3)
175}
176
177// inject2 appends two new events into the event log.
178func inject2(e *EventLog, pcr int, data1, data2 string) error {
179 if err := inject(e, pcr, data1); err != nil {
180 return err
181 }
182 return inject(e, pcr, data2)
183}
184
185// inject appends a new event into the event log.
186func inject(e *EventLog, pcr int, data string) error {
187 evt := rawEvent{
188 data: []byte(data),
189 index: pcr,
190 sequence: e.rawEvents[len(e.rawEvents)-1].sequence + 1,
191 }
192 for _, alg := range e.Algs {
193 h := alg.cryptoHash().New()
194 h.Write([]byte(data))
195 evt.digests = append(evt.digests, digest{hash: alg.cryptoHash(), data: h.Sum(nil)})
196 }
197 e.rawEvents = append(e.rawEvents, evt)
198 return nil
199}
200
201const (
202 ebsInvocation = "Exit Boot Services Invocation"
203 ebsSuccess = "Exit Boot Services Returned with Success"
204 ebsFailure = "Exit Boot Services Returned with Failure"
205)
206
207var eventlogWorkarounds = []elWorkaround{
208 {
209 id: "EBS Invocation + Success",
210 affectedPCR: 5,
211 apply: func(e *EventLog) error {
212 return inject2(e, 5, ebsInvocation, ebsSuccess)
213 },
214 },
215 {
216 id: "EBS Invocation + Failure",
217 affectedPCR: 5,
218 apply: func(e *EventLog) error {
219 return inject2(e, 5, ebsInvocation, ebsFailure)
220 },
221 },
222 {
223 id: "EBS Invocation + Failure + Success",
224 affectedPCR: 5,
225 apply: func(e *EventLog) error {
226 return inject3(e, 5, ebsInvocation, ebsFailure, ebsSuccess)
227 },
228 },
229}
230
231// Verify replays the event log against a TPM's PCR values, returning the
232// events which could be matched to a provided PCR value.
233// An error is returned if the replayed digest for events with a given PCR
234// index do not match any provided value for that PCR index.
235func (e *EventLog) Verify(pcrs []PCR) ([]Event, error) {
Tim Windelschmidt62050ca2024-04-22 18:29:35 +0200236 events, rErr := replayEvents(e.rawEvents, pcrs)
237 if rErr == nil {
238 return events, nil
239 }
Lorenz Bruna50e8452020-09-09 17:09:27 +0200240 // If there were any issues replaying the PCRs, try each of the workarounds
241 // in turn.
242 // TODO(jsonp): Allow workarounds to be combined.
Tim Windelschmidt62050ca2024-04-22 18:29:35 +0200243 for _, wkrd := range eventlogWorkarounds {
244 if !rErr.affected(wkrd.affectedPCR) {
245 continue
246 }
247 el := e.clone()
248 if err := wkrd.apply(el); err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200249 return nil, fmt.Errorf("failed applying workaround %q: %w", wkrd.id, err)
Tim Windelschmidt62050ca2024-04-22 18:29:35 +0200250 }
251 if events, err := replayEvents(el.rawEvents, pcrs); err == nil {
252 return events, nil
Lorenz Bruna50e8452020-09-09 17:09:27 +0200253 }
254 }
255
Tim Windelschmidt62050ca2024-04-22 18:29:35 +0200256 return events, rErr
Lorenz Bruna50e8452020-09-09 17:09:27 +0200257}
258
259// PCR encapsulates the value of a PCR at a point in time.
260type PCR struct {
261 Index int
262 Digest []byte
263 DigestAlg crypto.Hash
264}
265
Lorenz Bruna50e8452020-09-09 17:09:27 +0200266func extend(pcr PCR, replay []byte, e rawEvent) (pcrDigest []byte, eventDigest []byte, err error) {
267 h := pcr.DigestAlg
268
269 for _, digest := range e.digests {
270 if digest.hash != pcr.DigestAlg {
271 continue
272 }
273 if len(digest.data) != len(pcr.Digest) {
274 return nil, nil, fmt.Errorf("digest data length (%d) doesn't match PCR digest length (%d)", len(digest.data), len(pcr.Digest))
275 }
276 hash := h.New()
277 if len(replay) != 0 {
278 hash.Write(replay)
279 } else {
280 b := make([]byte, h.Size())
281 hash.Write(b)
282 }
283 hash.Write(digest.data)
284 return hash.Sum(nil), digest.data, nil
285 }
286 return nil, nil, fmt.Errorf("no event digest matches pcr algorithm: %v", pcr.DigestAlg)
287}
288
289// replayPCR replays the event log for a specific PCR, using pcr and
290// event digests with the algorithm in pcr. An error is returned if the
291// replayed values do not match the final PCR digest, or any event tagged
292// with that PCR does not posess an event digest with the specified algorithm.
293func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
294 var (
295 replay []byte
296 outEvents []Event
297 )
298
299 for _, e := range rawEvents {
300 if e.index != pcr.Index {
301 continue
302 }
303
304 replayValue, digest, err := extend(pcr, replay, e)
305 if err != nil {
306 return nil, false
307 }
308 replay = replayValue
309 outEvents = append(outEvents, Event{sequence: e.sequence, Data: e.data, Digest: digest, Index: pcr.Index, Type: e.typ})
310 }
311
312 if len(outEvents) > 0 && !bytes.Equal(replay, pcr.Digest) {
313 return nil, false
314 }
315 return outEvents, true
316}
317
318type pcrReplayResult struct {
319 events []Event
320 successful bool
321}
322
Tim Windelschmidt62050ca2024-04-22 18:29:35 +0200323func replayEvents(rawEvents []rawEvent, pcrs []PCR) ([]Event, *ReplayError) {
Lorenz Bruna50e8452020-09-09 17:09:27 +0200324 var (
325 invalidReplays []int
326 verifiedEvents []Event
327 allPCRReplays = map[int][]pcrReplayResult{}
328 )
329
330 // Replay the event log for every PCR and digest algorithm combination.
331 for _, pcr := range pcrs {
332 events, ok := replayPCR(rawEvents, pcr)
333 allPCRReplays[pcr.Index] = append(allPCRReplays[pcr.Index], pcrReplayResult{events, ok})
334 }
335
336 // Record PCR indices which do not have any successful replay. Record the
337 // events for a successful replay.
338pcrLoop:
339 for i, replaysForPCR := range allPCRReplays {
340 for _, replay := range replaysForPCR {
341 if replay.successful {
342 // We consider the PCR verified at this stage: The replay of values with
343 // one digest algorithm matched a provided value.
344 // As such, we save the PCR's events, and proceed to the next PCR.
345 verifiedEvents = append(verifiedEvents, replay.events...)
346 continue pcrLoop
347 }
348 }
349 invalidReplays = append(invalidReplays, i)
350 }
351
352 if len(invalidReplays) > 0 {
353 events := make([]Event, 0, len(rawEvents))
354 for _, e := range rawEvents {
355 events = append(events, Event{e.sequence, e.index, e.typ, e.data, nil})
356 }
Tim Windelschmidt62050ca2024-04-22 18:29:35 +0200357 return nil, &ReplayError{
Lorenz Bruna50e8452020-09-09 17:09:27 +0200358 Events: events,
359 invalidPCRs: invalidReplays,
360 }
361 }
362
363 sort.Slice(verifiedEvents, func(i int, j int) bool {
364 return verifiedEvents[i].sequence < verifiedEvents[j].sequence
365 })
366 return verifiedEvents, nil
367}
368
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200369// EV_NO_ACTION is a special event type that indicates information to the
370// parser instead of holding a measurement. For TPM 2.0, this event type is
371// used to signal switching from SHA1 format to a variable length digest.
Lorenz Bruna50e8452020-09-09 17:09:27 +0200372//
Tim Windelschmidt99e15112025-02-05 17:38:16 +0100373// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110
Lorenz Bruna50e8452020-09-09 17:09:27 +0200374const eventTypeNoAction = 0x03
375
376// ParseEventLog parses an unverified measurement log.
377func ParseEventLog(measurementLog []byte) (*EventLog, error) {
378 var specID *specIDEvent
379 r := bytes.NewBuffer(measurementLog)
380 parseFn := parseRawEvent
381 var el EventLog
382 e, err := parseFn(r, specID)
383 if err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200384 return nil, fmt.Errorf("parse first event: %w", err)
Lorenz Bruna50e8452020-09-09 17:09:27 +0200385 }
386 if e.typ == eventTypeNoAction {
387 specID, err = parseSpecIDEvent(e.data)
388 if err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200389 return nil, fmt.Errorf("failed to parse spec ID event: %w", err)
Lorenz Bruna50e8452020-09-09 17:09:27 +0200390 }
391 for _, alg := range specID.algs {
392 switch tpm2.Algorithm(alg.ID) {
393 case tpm2.AlgSHA1:
394 el.Algs = append(el.Algs, HashSHA1)
395 case tpm2.AlgSHA256:
396 el.Algs = append(el.Algs, HashSHA256)
397 }
398 }
399 if len(el.Algs) == 0 {
400 return nil, fmt.Errorf("measurement log didn't use sha1 or sha256 digests")
401 }
402 // Switch to parsing crypto agile events. Don't include this in the
403 // replayed events since it intentionally doesn't extend the PCRs.
404 //
405 // Note that this doesn't actually guarentee that events have SHA256
406 // digests.
407 parseFn = parseRawEvent2
408 } else {
409 el.Algs = []HashAlg{HashSHA1}
410 el.rawEvents = append(el.rawEvents, e)
411 }
412 sequence := 1
413 for r.Len() != 0 {
414 e, err := parseFn(r, specID)
415 if err != nil {
416 return nil, err
417 }
418 e.sequence = sequence
419 sequence++
420 el.rawEvents = append(el.rawEvents, e)
421 }
422 return &el, nil
423}
424
425type specIDEvent struct {
426 algs []specAlgSize
427}
428
429type specAlgSize struct {
430 ID uint16
431 Size uint16
432}
433
434// Expected values for various Spec ID Event fields.
Tim Windelschmidt99e15112025-02-05 17:38:16 +0100435//
436// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=19
Lorenz Bruna50e8452020-09-09 17:09:27 +0200437var wantSignature = [16]byte{0x53, 0x70,
438 0x65, 0x63, 0x20, 0x49,
439 0x44, 0x20, 0x45, 0x76,
440 0x65, 0x6e, 0x74, 0x30,
441 0x33, 0x00} // "Spec ID Event03\0"
442
443const (
444 wantMajor = 2
445 wantMinor = 0
446 wantErrata = 0
447)
448
449// parseSpecIDEvent parses a TCG_EfiSpecIDEventStruct structure from the reader.
Tim Windelschmidt99e15112025-02-05 17:38:16 +0100450//
451// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=18
Lorenz Bruna50e8452020-09-09 17:09:27 +0200452func parseSpecIDEvent(b []byte) (*specIDEvent, error) {
453 r := bytes.NewReader(b)
454 var header struct {
455 Signature [16]byte
456 PlatformClass uint32
457 VersionMinor uint8
458 VersionMajor uint8
459 Errata uint8
460 UintnSize uint8
461 NumAlgs uint32
462 }
463 if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200464 return nil, fmt.Errorf("reading event header: %w", err)
Lorenz Bruna50e8452020-09-09 17:09:27 +0200465 }
466 if header.Signature != wantSignature {
467 return nil, fmt.Errorf("invalid spec id signature: %x", header.Signature)
468 }
469 if header.VersionMajor != wantMajor {
470 return nil, fmt.Errorf("invalid spec major version, got %02x, wanted %02x",
471 header.VersionMajor, wantMajor)
472 }
473 if header.VersionMinor != wantMinor {
474 return nil, fmt.Errorf("invalid spec minor version, got %02x, wanted %02x",
475 header.VersionMajor, wantMinor)
476 }
477
478 // TODO(ericchiang): Check errata? Or do we expect that to change in ways
479 // we're okay with?
480
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +0200481 var specAlg specAlgSize
482 var e specIDEvent
Lorenz Bruna50e8452020-09-09 17:09:27 +0200483 for i := 0; i < int(header.NumAlgs); i++ {
484 if err := binary.Read(r, binary.LittleEndian, &specAlg); err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200485 return nil, fmt.Errorf("reading algorithm: %w", err)
Lorenz Bruna50e8452020-09-09 17:09:27 +0200486 }
487 e.algs = append(e.algs, specAlg)
488 }
489
490 var vendorInfoSize uint8
491 if err := binary.Read(r, binary.LittleEndian, &vendorInfoSize); err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200492 return nil, fmt.Errorf("reading vender info size: %w", err)
Lorenz Bruna50e8452020-09-09 17:09:27 +0200493 }
494 if r.Len() != int(vendorInfoSize) {
495 return nil, fmt.Errorf("reading vendor info, expected %d remaining bytes, got %d", vendorInfoSize, r.Len())
496 }
497 return &e, nil
498}
499
500type digest struct {
501 hash crypto.Hash
502 data []byte
503}
504
505type rawEvent struct {
506 sequence int
507 index int
508 typ EventType
509 data []byte
510 digests []digest
511}
512
513// TPM 1.2 event log format. See "5.1 SHA1 Event Log Entry Format"
Tim Windelschmidt99e15112025-02-05 17:38:16 +0100514//
515// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
Lorenz Bruna50e8452020-09-09 17:09:27 +0200516type rawEventHeader struct {
517 PCRIndex uint32
518 Type uint32
519 Digest [20]byte
520 EventSize uint32
521}
522
523type eventSizeErr struct {
524 eventSize uint32
525 logSize int
526}
527
528func (e *eventSizeErr) Error() string {
529 return fmt.Sprintf("event data size (%d bytes) is greater than remaining measurement log (%d bytes)", e.eventSize, e.logSize)
530}
531
532func parseRawEvent(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
533 var h rawEventHeader
534 if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
535 return event, err
536 }
537 if h.EventSize == 0 {
538 return event, errors.New("event data size is 0")
539 }
540 if h.EventSize > uint32(r.Len()) {
541 return event, &eventSizeErr{h.EventSize, r.Len()}
542 }
543
544 data := make([]byte, int(h.EventSize))
545 if _, err := io.ReadFull(r, data); err != nil {
546 return event, err
547 }
548
549 digests := []digest{{hash: crypto.SHA1, data: h.Digest[:]}}
550
551 return rawEvent{
552 typ: EventType(h.Type),
553 data: data,
554 index: int(h.PCRIndex),
555 digests: digests,
556 }, nil
557}
558
559// TPM 2.0 event log format. See "5.2 Crypto Agile Log Entry Format"
Tim Windelschmidt99e15112025-02-05 17:38:16 +0100560//
561// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
Lorenz Bruna50e8452020-09-09 17:09:27 +0200562type rawEvent2Header struct {
563 PCRIndex uint32
564 Type uint32
565}
566
567func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
568 var h rawEvent2Header
569
570 if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
571 return event, err
572 }
573 event.typ = EventType(h.Type)
574 event.index = int(h.PCRIndex)
575
576 // parse the event digests
577 var numDigests uint32
578 if err := binary.Read(r, binary.LittleEndian, &numDigests); err != nil {
579 return event, err
580 }
581
582 for i := 0; i < int(numDigests); i++ {
583 var algID uint16
584 if err := binary.Read(r, binary.LittleEndian, &algID); err != nil {
585 return event, err
586 }
587 var digest digest
588
589 for _, alg := range specID.algs {
590 if alg.ID != algID {
591 continue
592 }
593 if uint16(r.Len()) < alg.Size {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200594 return event, fmt.Errorf("reading digest: %w", io.ErrUnexpectedEOF)
Lorenz Bruna50e8452020-09-09 17:09:27 +0200595 }
596 digest.data = make([]byte, alg.Size)
597 digest.hash = HashAlg(alg.ID).cryptoHash()
598 }
599 if len(digest.data) == 0 {
600 return event, fmt.Errorf("unknown algorithm ID %x", algID)
601 }
602 if _, err := io.ReadFull(r, digest.data); err != nil {
603 return event, err
604 }
605 event.digests = append(event.digests, digest)
606 }
607
608 // parse event data
609 var eventSize uint32
610 if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
611 return event, err
612 }
613 if eventSize == 0 {
614 return event, errors.New("event data size is 0")
615 }
616 if eventSize > uint32(r.Len()) {
617 return event, &eventSizeErr{eventSize, r.Len()}
618 }
619 event.data = make([]byte, int(eventSize))
620 if _, err := io.ReadFull(r, event.data); err != nil {
621 return event, err
622 }
623 return event, err
624}