|  | // Copyright 2020 The Monogon Project Authors. | 
|  | // | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  | // | 
|  | // 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. | 
|  |  | 
|  | // Taken and pruned from go-attestation under Apache 2.0 | 
|  | package eventlog | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "errors" | 
|  | "fmt" | 
|  |  | 
|  | "github.com/google/certificate-transparency-go/x509" | 
|  |  | 
|  | "source.monogon.dev/metropolis/pkg/tpm/eventlog/internal" | 
|  | ) | 
|  |  | 
|  | // SecurebootState describes the secure boot status of a machine, as determined | 
|  | // by processing its event log. | 
|  | type SecurebootState struct { | 
|  | Enabled bool | 
|  |  | 
|  | // PlatformKeys enumerates keys which can sign a key exchange key. | 
|  | PlatformKeys []x509.Certificate | 
|  | // PlatformKeys enumerates key hashes which can sign a key exchange key. | 
|  | PlatformKeyHashes [][]byte | 
|  |  | 
|  | // ExchangeKeys enumerates keys which can sign a database of permitted or | 
|  | // forbidden keys. | 
|  | ExchangeKeys []x509.Certificate | 
|  | // ExchangeKeyHashes enumerates key hashes which can sign a database or | 
|  | // permitted or forbidden keys. | 
|  | ExchangeKeyHashes [][]byte | 
|  |  | 
|  | // PermittedKeys enumerates keys which may sign binaries to run. | 
|  | PermittedKeys []x509.Certificate | 
|  | // PermittedHashes enumerates hashes which permit binaries to run. | 
|  | PermittedHashes [][]byte | 
|  |  | 
|  | // ForbiddenKeys enumerates keys which must not permit a binary to run. | 
|  | ForbiddenKeys []x509.Certificate | 
|  | // ForbiddenKeys enumerates hashes which must not permit a binary to run. | 
|  | ForbiddenHashes [][]byte | 
|  |  | 
|  | // PreSeparatorAuthority describes the use of a secure-boot key to authorize | 
|  | // the execution of a binary before the separator. | 
|  | PreSeparatorAuthority []x509.Certificate | 
|  | // PostSeparatorAuthority describes the use of a secure-boot key to authorize | 
|  | // the execution of a binary after the separator. | 
|  | PostSeparatorAuthority []x509.Certificate | 
|  | } | 
|  |  | 
|  | // ParseSecurebootState parses a series of events to determine the | 
|  | // configuration of secure boot on a device. An error is returned if | 
|  | // the state cannot be determined, or if the event log is structured | 
|  | // in such a way that it may have been tampered post-execution of | 
|  | // platform firmware. | 
|  | func ParseSecurebootState(events []Event) (*SecurebootState, error) { | 
|  | // This algorithm verifies the following: | 
|  | // - All events in PCR 7 have event types which are expected in PCR 7. | 
|  | // - All events are parsable according to their event type. | 
|  | // - All events have digests values corresponding to their data/event type. | 
|  | // - No unverifiable events were present. | 
|  | // - All variables are specified before the separator and never duplicated. | 
|  | // - The SecureBoot variable has a value of 0 or 1. | 
|  | // - If SecureBoot was 1 (enabled), authority events were present indicating | 
|  | //   keys were used to perform verification. | 
|  | // - If SecureBoot was 1 (enabled), platform + exchange + database keys | 
|  | //   were specified. | 
|  | // - No UEFI debugger was attached. | 
|  |  | 
|  | var ( | 
|  | out           SecurebootState | 
|  | seenSeparator bool | 
|  | seenAuthority bool | 
|  | seenVars      = map[string]bool{} | 
|  | ) | 
|  |  | 
|  | for _, e := range events { | 
|  | if e.Index != 7 { | 
|  | continue | 
|  | } | 
|  |  | 
|  | et, err := internal.UntrustedParseEventType(uint32(e.Type)) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("unrecognised event type: %v", err) | 
|  | } | 
|  |  | 
|  | digestVerify := e.digestEquals(e.Data) | 
|  | switch et { | 
|  | case internal.Separator: | 
|  | if seenSeparator { | 
|  | return nil, fmt.Errorf("duplicate separator at event %d", e.sequence) | 
|  | } | 
|  | seenSeparator = true | 
|  | if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) { | 
|  | return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data) | 
|  | } | 
|  | if digestVerify != nil { | 
|  | return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify) | 
|  | } | 
|  |  | 
|  | case internal.EFIAction: | 
|  | if string(e.Data) == "UEFI Debug Mode" { | 
|  | return nil, errors.New("a UEFI debugger was present during boot") | 
|  | } | 
|  | return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence) | 
|  |  | 
|  | case internal.EFIVariableDriverConfig: | 
|  | v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data)) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err) | 
|  | } | 
|  | if _, seenBefore := seenVars[v.VarName()]; seenBefore { | 
|  | return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence) | 
|  | } | 
|  | seenVars[v.VarName()] = true | 
|  | if seenSeparator { | 
|  | return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName()) | 
|  | } | 
|  |  | 
|  | if digestVerify != nil { | 
|  | return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify) | 
|  | } | 
|  |  | 
|  | switch v.VarName() { | 
|  | case "SecureBoot": | 
|  | if len(v.VariableData) != 1 { | 
|  | return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData)) | 
|  | } | 
|  | out.Enabled = v.VariableData[0] == 1 | 
|  | case "PK": | 
|  | if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil { | 
|  | return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err) | 
|  | } | 
|  | case "KEK": | 
|  | if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil { | 
|  | return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err) | 
|  | } | 
|  | case "db": | 
|  | if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil { | 
|  | return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err) | 
|  | } | 
|  | case "dbx": | 
|  | if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil { | 
|  | return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err) | 
|  | } | 
|  | } | 
|  |  | 
|  | case internal.EFIVariableAuthority: | 
|  | a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data)) | 
|  | if err != nil { | 
|  | // Workaround for: https://github.com/google/go-attestation/issues/157 | 
|  | if err == internal.ErrSigMissingGUID { | 
|  | // Versions of shim which do not carry | 
|  | // https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50 | 
|  | // have an erroneous additional byte in the event, which breaks digest | 
|  | // verification. If verification failed, we try removing the last byte. | 
|  | if digestVerify != nil { | 
|  | digestVerify = e.digestEquals(e.Data[:len(e.Data)-1]) | 
|  | } | 
|  | } else { | 
|  | return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err) | 
|  | } | 
|  | } | 
|  | seenAuthority = true | 
|  | if digestVerify != nil { | 
|  | return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify) | 
|  | } | 
|  | if !seenSeparator { | 
|  | out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...) | 
|  | } else { | 
|  | out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...) | 
|  | } | 
|  |  | 
|  | default: | 
|  | return nil, fmt.Errorf("unexpected event type: %v", et) | 
|  | } | 
|  | } | 
|  |  | 
|  | if !out.Enabled { | 
|  | return &out, nil | 
|  | } | 
|  |  | 
|  | if !seenAuthority { | 
|  | return nil, errors.New("secure boot was enabled but no key was used") | 
|  | } | 
|  | if len(out.PlatformKeys) == 0 && len(out.PlatformKeyHashes) == 0 { | 
|  | return nil, errors.New("secure boot was enabled but no platform keys were known") | 
|  | } | 
|  | if len(out.ExchangeKeys) == 0 && len(out.ExchangeKeyHashes) == 0 { | 
|  | return nil, errors.New("secure boot was enabled but no key exchange keys were known") | 
|  | } | 
|  | if len(out.PermittedKeys) == 0 && len(out.PermittedHashes) == 0 { | 
|  | return nil, errors.New("secure boot was enabled but no keys or hashes were permitted") | 
|  | } | 
|  | return &out, nil | 
|  | } |