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