blob: 7201a8966676eeed97dc7685bd43d01068da2477 [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//
Serge Bazanski216fe7b2021-05-21 18:36:16 +020099// 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) {
249 events, err := e.verify(pcrs)
250 // If there were any issues replaying the PCRs, try each of the workarounds
251 // in turn.
252 // TODO(jsonp): Allow workarounds to be combined.
253 if rErr, isReplayErr := err.(ReplayError); isReplayErr {
254 for _, wkrd := range eventlogWorkarounds {
255 if !rErr.affected(wkrd.affectedPCR) {
256 continue
257 }
258 el := e.clone()
259 if err := wkrd.apply(el); err != nil {
260 return nil, fmt.Errorf("failed applying workaround %q: %v", wkrd.id, err)
261 }
262 if events, err := el.verify(pcrs); err == nil {
263 return events, nil
264 }
265 }
266 }
267
268 return events, err
269}
270
271// PCR encapsulates the value of a PCR at a point in time.
272type PCR struct {
273 Index int
274 Digest []byte
275 DigestAlg crypto.Hash
276}
277
278func (e *EventLog) verify(pcrs []PCR) ([]Event, error) {
279 events, err := replayEvents(e.rawEvents, pcrs)
280 if err != nil {
281 if _, isReplayErr := err.(ReplayError); isReplayErr {
282 return nil, err
283 }
284 return nil, fmt.Errorf("pcrs failed to replay: %v", err)
285 }
286 return events, nil
287}
288
289func extend(pcr PCR, replay []byte, e rawEvent) (pcrDigest []byte, eventDigest []byte, err error) {
290 h := pcr.DigestAlg
291
292 for _, digest := range e.digests {
293 if digest.hash != pcr.DigestAlg {
294 continue
295 }
296 if len(digest.data) != len(pcr.Digest) {
297 return nil, nil, fmt.Errorf("digest data length (%d) doesn't match PCR digest length (%d)", len(digest.data), len(pcr.Digest))
298 }
299 hash := h.New()
300 if len(replay) != 0 {
301 hash.Write(replay)
302 } else {
303 b := make([]byte, h.Size())
304 hash.Write(b)
305 }
306 hash.Write(digest.data)
307 return hash.Sum(nil), digest.data, nil
308 }
309 return nil, nil, fmt.Errorf("no event digest matches pcr algorithm: %v", pcr.DigestAlg)
310}
311
312// replayPCR replays the event log for a specific PCR, using pcr and
313// event digests with the algorithm in pcr. An error is returned if the
314// replayed values do not match the final PCR digest, or any event tagged
315// with that PCR does not posess an event digest with the specified algorithm.
316func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
317 var (
318 replay []byte
319 outEvents []Event
320 )
321
322 for _, e := range rawEvents {
323 if e.index != pcr.Index {
324 continue
325 }
326
327 replayValue, digest, err := extend(pcr, replay, e)
328 if err != nil {
329 return nil, false
330 }
331 replay = replayValue
332 outEvents = append(outEvents, Event{sequence: e.sequence, Data: e.data, Digest: digest, Index: pcr.Index, Type: e.typ})
333 }
334
335 if len(outEvents) > 0 && !bytes.Equal(replay, pcr.Digest) {
336 return nil, false
337 }
338 return outEvents, true
339}
340
341type pcrReplayResult struct {
342 events []Event
343 successful bool
344}
345
346func replayEvents(rawEvents []rawEvent, pcrs []PCR) ([]Event, error) {
347 var (
348 invalidReplays []int
349 verifiedEvents []Event
350 allPCRReplays = map[int][]pcrReplayResult{}
351 )
352
353 // Replay the event log for every PCR and digest algorithm combination.
354 for _, pcr := range pcrs {
355 events, ok := replayPCR(rawEvents, pcr)
356 allPCRReplays[pcr.Index] = append(allPCRReplays[pcr.Index], pcrReplayResult{events, ok})
357 }
358
359 // Record PCR indices which do not have any successful replay. Record the
360 // events for a successful replay.
361pcrLoop:
362 for i, replaysForPCR := range allPCRReplays {
363 for _, replay := range replaysForPCR {
364 if replay.successful {
365 // We consider the PCR verified at this stage: The replay of values with
366 // one digest algorithm matched a provided value.
367 // As such, we save the PCR's events, and proceed to the next PCR.
368 verifiedEvents = append(verifiedEvents, replay.events...)
369 continue pcrLoop
370 }
371 }
372 invalidReplays = append(invalidReplays, i)
373 }
374
375 if len(invalidReplays) > 0 {
376 events := make([]Event, 0, len(rawEvents))
377 for _, e := range rawEvents {
378 events = append(events, Event{e.sequence, e.index, e.typ, e.data, nil})
379 }
380 return nil, ReplayError{
381 Events: events,
382 invalidPCRs: invalidReplays,
383 }
384 }
385
386 sort.Slice(verifiedEvents, func(i int, j int) bool {
387 return verifiedEvents[i].sequence < verifiedEvents[j].sequence
388 })
389 return verifiedEvents, nil
390}
391
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200392// EV_NO_ACTION is a special event type that indicates information to the
393// parser instead of holding a measurement. For TPM 2.0, this event type is
394// used to signal switching from SHA1 format to a variable length digest.
Lorenz Bruna50e8452020-09-09 17:09:27 +0200395//
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200396// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110
Lorenz Bruna50e8452020-09-09 17:09:27 +0200397const eventTypeNoAction = 0x03
398
399// ParseEventLog parses an unverified measurement log.
400func ParseEventLog(measurementLog []byte) (*EventLog, error) {
401 var specID *specIDEvent
402 r := bytes.NewBuffer(measurementLog)
403 parseFn := parseRawEvent
404 var el EventLog
405 e, err := parseFn(r, specID)
406 if err != nil {
407 return nil, fmt.Errorf("parse first event: %v", err)
408 }
409 if e.typ == eventTypeNoAction {
410 specID, err = parseSpecIDEvent(e.data)
411 if err != nil {
412 return nil, fmt.Errorf("failed to parse spec ID event: %v", err)
413 }
414 for _, alg := range specID.algs {
415 switch tpm2.Algorithm(alg.ID) {
416 case tpm2.AlgSHA1:
417 el.Algs = append(el.Algs, HashSHA1)
418 case tpm2.AlgSHA256:
419 el.Algs = append(el.Algs, HashSHA256)
420 }
421 }
422 if len(el.Algs) == 0 {
423 return nil, fmt.Errorf("measurement log didn't use sha1 or sha256 digests")
424 }
425 // Switch to parsing crypto agile events. Don't include this in the
426 // replayed events since it intentionally doesn't extend the PCRs.
427 //
428 // Note that this doesn't actually guarentee that events have SHA256
429 // digests.
430 parseFn = parseRawEvent2
431 } else {
432 el.Algs = []HashAlg{HashSHA1}
433 el.rawEvents = append(el.rawEvents, e)
434 }
435 sequence := 1
436 for r.Len() != 0 {
437 e, err := parseFn(r, specID)
438 if err != nil {
439 return nil, err
440 }
441 e.sequence = sequence
442 sequence++
443 el.rawEvents = append(el.rawEvents, e)
444 }
445 return &el, nil
446}
447
448type specIDEvent struct {
449 algs []specAlgSize
450}
451
452type specAlgSize struct {
453 ID uint16
454 Size uint16
455}
456
457// Expected values for various Spec ID Event fields.
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200458// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=19
Lorenz Bruna50e8452020-09-09 17:09:27 +0200459var wantSignature = [16]byte{0x53, 0x70,
460 0x65, 0x63, 0x20, 0x49,
461 0x44, 0x20, 0x45, 0x76,
462 0x65, 0x6e, 0x74, 0x30,
463 0x33, 0x00} // "Spec ID Event03\0"
464
465const (
466 wantMajor = 2
467 wantMinor = 0
468 wantErrata = 0
469)
470
471// parseSpecIDEvent parses a TCG_EfiSpecIDEventStruct structure from the reader.
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200472// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=18
Lorenz Bruna50e8452020-09-09 17:09:27 +0200473func parseSpecIDEvent(b []byte) (*specIDEvent, error) {
474 r := bytes.NewReader(b)
475 var header struct {
476 Signature [16]byte
477 PlatformClass uint32
478 VersionMinor uint8
479 VersionMajor uint8
480 Errata uint8
481 UintnSize uint8
482 NumAlgs uint32
483 }
484 if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
485 return nil, fmt.Errorf("reading event header: %v", err)
486 }
487 if header.Signature != wantSignature {
488 return nil, fmt.Errorf("invalid spec id signature: %x", header.Signature)
489 }
490 if header.VersionMajor != wantMajor {
491 return nil, fmt.Errorf("invalid spec major version, got %02x, wanted %02x",
492 header.VersionMajor, wantMajor)
493 }
494 if header.VersionMinor != wantMinor {
495 return nil, fmt.Errorf("invalid spec minor version, got %02x, wanted %02x",
496 header.VersionMajor, wantMinor)
497 }
498
499 // TODO(ericchiang): Check errata? Or do we expect that to change in ways
500 // we're okay with?
501
502 specAlg := specAlgSize{}
503 e := specIDEvent{}
504 for i := 0; i < int(header.NumAlgs); i++ {
505 if err := binary.Read(r, binary.LittleEndian, &specAlg); err != nil {
506 return nil, fmt.Errorf("reading algorithm: %v", err)
507 }
508 e.algs = append(e.algs, specAlg)
509 }
510
511 var vendorInfoSize uint8
512 if err := binary.Read(r, binary.LittleEndian, &vendorInfoSize); err != nil {
513 return nil, fmt.Errorf("reading vender info size: %v", err)
514 }
515 if r.Len() != int(vendorInfoSize) {
516 return nil, fmt.Errorf("reading vendor info, expected %d remaining bytes, got %d", vendorInfoSize, r.Len())
517 }
518 return &e, nil
519}
520
521type digest struct {
522 hash crypto.Hash
523 data []byte
524}
525
526type rawEvent struct {
527 sequence int
528 index int
529 typ EventType
530 data []byte
531 digests []digest
532}
533
534// TPM 1.2 event log format. See "5.1 SHA1 Event Log Entry Format"
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200535// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
Lorenz Bruna50e8452020-09-09 17:09:27 +0200536type rawEventHeader struct {
537 PCRIndex uint32
538 Type uint32
539 Digest [20]byte
540 EventSize uint32
541}
542
543type eventSizeErr struct {
544 eventSize uint32
545 logSize int
546}
547
548func (e *eventSizeErr) Error() string {
549 return fmt.Sprintf("event data size (%d bytes) is greater than remaining measurement log (%d bytes)", e.eventSize, e.logSize)
550}
551
552func parseRawEvent(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
553 var h rawEventHeader
554 if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
555 return event, err
556 }
557 if h.EventSize == 0 {
558 return event, errors.New("event data size is 0")
559 }
560 if h.EventSize > uint32(r.Len()) {
561 return event, &eventSizeErr{h.EventSize, r.Len()}
562 }
563
564 data := make([]byte, int(h.EventSize))
565 if _, err := io.ReadFull(r, data); err != nil {
566 return event, err
567 }
568
569 digests := []digest{{hash: crypto.SHA1, data: h.Digest[:]}}
570
571 return rawEvent{
572 typ: EventType(h.Type),
573 data: data,
574 index: int(h.PCRIndex),
575 digests: digests,
576 }, nil
577}
578
579// TPM 2.0 event log format. See "5.2 Crypto Agile Log Entry Format"
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200580// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
Lorenz Bruna50e8452020-09-09 17:09:27 +0200581type rawEvent2Header struct {
582 PCRIndex uint32
583 Type uint32
584}
585
586func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
587 var h rawEvent2Header
588
589 if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
590 return event, err
591 }
592 event.typ = EventType(h.Type)
593 event.index = int(h.PCRIndex)
594
595 // parse the event digests
596 var numDigests uint32
597 if err := binary.Read(r, binary.LittleEndian, &numDigests); err != nil {
598 return event, err
599 }
600
601 for i := 0; i < int(numDigests); i++ {
602 var algID uint16
603 if err := binary.Read(r, binary.LittleEndian, &algID); err != nil {
604 return event, err
605 }
606 var digest digest
607
608 for _, alg := range specID.algs {
609 if alg.ID != algID {
610 continue
611 }
612 if uint16(r.Len()) < alg.Size {
613 return event, fmt.Errorf("reading digest: %v", io.ErrUnexpectedEOF)
614 }
615 digest.data = make([]byte, alg.Size)
616 digest.hash = HashAlg(alg.ID).cryptoHash()
617 }
618 if len(digest.data) == 0 {
619 return event, fmt.Errorf("unknown algorithm ID %x", algID)
620 }
621 if _, err := io.ReadFull(r, digest.data); err != nil {
622 return event, err
623 }
624 event.digests = append(event.digests, digest)
625 }
626
627 // parse event data
628 var eventSize uint32
629 if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
630 return event, err
631 }
632 if eventSize == 0 {
633 return event, errors.New("event data size is 0")
634 }
635 if eventSize > uint32(r.Len()) {
636 return event, &eventSizeErr{eventSize, r.Len()}
637 }
638 event.data = make([]byte, int(eventSize))
639 if _, err := io.ReadFull(r, event.data); err != nil {
640 return event, err
641 }
642 return event, err
643}