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