From ba42909a47f7cc159d06776a13059e0a0df16b5e Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 2 Oct 2024 22:34:05 +0100 Subject: [PATCH 1/6] preinstall: Add checks for PCR7 This adds some checks for PCR7. The caller supplies a context.Context to which an EFI variable backend is attached, a internal_efi.HostEnvironment implementation, a TCG log, a PCR digest algorith (the optimum for this is computed earlier by another function that does a more general check of the TCG log) and the image of the initial boot loader for the current boot. As we're testing the firmware, this only checks the log up to the launch of the initial boot loader. It ignores events after this, as these are under the control of the OS and don't rely on any special firmware features such as EFI_TCG2_PROTOCOL + PE_COFF_IMAGE vs EFI_TCG_PROTOCOL in the same way that OS components measuring to PCR4 do. This ensures that secure boot is enabled, else an error is returned, as WithSecureBootPolicyProfile only generates profiles compatible with secure boot being enabled. If the version of UEFI is >= 2.5, it also makes sure that the secure boot mode is "deployed mode". If the secure boot mode is "user mode", then the "AuditMode" and "DeployedMode" values are measured to PCR7, something that WithSecureBootPolicyProfile doesn't support today. Support for "user mode" will be added in the future, although the public RunChecks API will probably require a flag to opt in to supporting user mode, as it is the less secure mode of the 2 (see the documentation for SecureBootMode in github.com/canonical/go-efilib). It also reads the "OsIndicationsSupported" variable to test for features that are not supported by WithSecureBootPolicyProfile. These are timestamp revocation (which requires an extra signature database - "dbt") and OS recovery (which requires an extra signature database - "dbr", used to control access to OsRecoveryOrder and OsRecover#### variables). Of the 2, it's likely that we might need to add support for timestamp revocation at some point. It reads the "BootCurrent" EFI variable and matches this to the EFI_LOAD_OPTION associated with the current boot from the TCG log - it uses the log as "BootXXXX" EFI variables can be updated at runtime and might be out of data when this code runs. It uses this to detect the launch of the initial boot loader, which might not necessarily be the first EV_EFI_BOOT_SERVICES_APPLICATION event in the OS-present environment in PCR4 (eg, if Absolute is active). First of all, it iterates over the secure boot configuration in the log, making sure that the configuration is measured in the correct order, that the event data is valid, and that the measured digest is the tagged hash of the event data. It makes sure that the value of "SecureBoot" in the log is consistent with the "SecureBoot" variable (which is read-only at runtime), and it verifies that all of the signature databases are formatted correctly. It will return an error if any of these checks fail. If the pre-OS environment contains events other than EV_EFI_VARIABLE_DRIVER_CONFIG, it will return an error. This can happen if a firmware debugger is enabled, in which case PCR7 will begin with a EV_EFI_ACTION "UEFI Debug Mode" event. This case is detected by earlier firmware protection checks. If not all of the expected secure boot configuration is measured, an error is returned. Once the secure boot configuration has been measured, it looks for EV_EFI_VARIABLE_AUTHORITY events in PCR7, until it detects the launch of the initial boot loader. It verifies that each of these come from db, and if the log is in the OS-present environment, it ensures that the measured digest is the tagged hash of the event data. It doesn't do this for events in the pre-OS environment because WithSecureBootPolicyProfile just copies these to the profile. It verifies that the firmware doesn't measure a signature more than once. For each EV_EFI_VARIABLE_AUTHORITY event, it also matches the measured signature to a EFI_SIGNATURE_LIST structure in db. If the matched ESL is a X.509 certificate, it records the use of this CA in the return value. If the CA is an RSA certificate with a public modulus of <= 1024 bits, it sets a flag in the return value indicating a weak algorithm. If the matched ESL is a Authenticode digest, it sets a flag in the return value indicating that pre-OS components were verified using digests rather than signatures. This makes PCR7 fragile wrt firmware updates, because it means db needs to be updated to reflect the new components each time. If the digest being matched is SHA-1, it sets the flag in the return value indicating a weak algorithm. If any of these checks fail, an error is returned. If an event type other than EV_EFI_VARIABLE_AUTHORITY is detected, an error is returned. Upon detecting the launch of the initial boot loader in PCR4, it extracts the authenticode signatures from the supplied image, and matches these to a previously measured CA. If no match is found, an error is returned. If a match is found, it ensures that the signing certificate has an RSA public key with a modulus larger than 1024 bits, else it sets a flag in the return value indicating a weak algorithm. Once the event for the initial boot loader is complete, the function returns. If the end of the log is reached without encountering the launch of the initial boot loader, an error is returned. --- efi/pe.go | 65 +- efi/preinstall/check_pcr4_test.go | 38 - efi/preinstall/check_pcr7.go | 529 ++++++++ efi/preinstall/check_pcr7_test.go | 1120 +++++++++++++++++ efi/preinstall/check_tcglog.go | 10 +- efi/preinstall/check_tcglog_test.go | 57 - efi/preinstall/errors.go | 12 + efi/preinstall/export_test.go | 56 +- efi/preinstall/preinstall_test.go | 77 ++ efi/preinstall/testdata/MicrosoftUefiCA.crt | 35 + efi/preinstall/testdata/PkKek-1-snakeoil.pem | 19 + ...signed_1.54+15.7-0ubuntu1_amd64_latest.pk7 | 202 +++ internal/efi/pe.go | 95 ++ internal/efitest/db.go | 22 + internal/efitest/log.go | 94 +- 15 files changed, 2229 insertions(+), 202 deletions(-) create mode 100644 efi/preinstall/check_pcr7.go create mode 100644 efi/preinstall/check_pcr7_test.go create mode 100644 efi/preinstall/testdata/MicrosoftUefiCA.crt create mode 100644 efi/preinstall/testdata/PkKek-1-snakeoil.pem create mode 100644 efi/preinstall/testdata/shim-signed_1.54+15.7-0ubuntu1_amd64_latest.pk7 create mode 100644 internal/efi/pe.go diff --git a/efi/pe.go b/efi/pe.go index 6091bc45..c81bf562 100644 --- a/efi/pe.go +++ b/efi/pe.go @@ -24,13 +24,13 @@ import ( "crypto" "encoding/csv" "errors" - "fmt" "io" "strconv" efi "github.com/canonical/go-efilib" "golang.org/x/xerrors" + internal_efi "github.com/snapcore/secboot/internal/efi" pe "github.com/snapcore/secboot/internal/pe1.14" ) @@ -179,73 +179,12 @@ func (h *peImageHandleImpl) SbatComponents() ([]sbatComponent, error) { return components, nil } -const ( - certTableIndex = 4 // Index of the Certificate Table entry in the Data Directory of a PE image optional header -) - func (h *peImageHandleImpl) ImageDigest(alg crypto.Hash) ([]byte, error) { return efi.ComputePeImageDigest(alg, h.r, h.r.Size()) } func (h *peImageHandleImpl) SecureBootSignatures() ([]*efi.WinCertificateAuthenticode, error) { - // Obtain security directory entry from optional header - var dd []pe.DataDirectory - switch oh := h.pefile.OptionalHeader.(type) { - case *pe.OptionalHeader32: - dd = oh.DataDirectory[0:oh.NumberOfRvaAndSizes] - case *pe.OptionalHeader64: - dd = oh.DataDirectory[0:oh.NumberOfRvaAndSizes] - default: - return nil, errors.New("cannot obtain security directory entry: no optional header") - } - - if len(dd) <= certTableIndex { - // This image doesn't include a certificate table entry, so has no signatures. - return nil, nil - } - - // Create a reader for the security directory entry, which points to one or more WIN_CERTIFICATE structs - certReader := io.NewSectionReader( - h.r, - int64(dd[certTableIndex].VirtualAddress), - int64(dd[certTableIndex].Size)) - - // Binaries can have multiple signers - this is achieved using multiple single-signed Authenticode - // signatures - see section 32.5.3.3 ("Secure Boot and Driver Signing - UEFI Image Validation - - // Signature Database Update - Authorization Process") of the UEFI Specification, version 2.8. - var sigs []*efi.WinCertificateAuthenticode - -SignatureLoop: - for i := 0; ; i++ { - // Signatures in this section are 8-byte aligned - see the PE spec: - // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only - off, _ := certReader.Seek(0, io.SeekCurrent) - alignSize := (8 - (off & 7)) % 8 - certReader.Seek(alignSize, io.SeekCurrent) - - c, err := efi.ReadWinCertificate(certReader) - switch { - case xerrors.Is(err, io.EOF): - break SignatureLoop - case err != nil: - return nil, xerrors.Errorf("cannot decode WIN_CERTIFICATE from security directory entry %d: %w", i, err) - } - - sig, ok := c.(*efi.WinCertificateAuthenticode) - if !ok { - return nil, fmt.Errorf("unexpected WIN_CERTIFICATE type from security directory entry %d: not an Authenticode signature", i) - } - - // Reject any signature with a digest algorithm other than SHA256, as that's the only algorithm used - // for binaries we're expected to support, and therefore required by the UEFI implementation. - if sig.DigestAlgorithm() != crypto.SHA256 { - return nil, fmt.Errorf("signature from security directory entry %d has unexpected digest algorithm", i) - } - - sigs = append(sigs, sig) - } - - return sigs, nil + return internal_efi.SecureBootSignaturesFromPEFile(h.pefile, h.r) } // cstringReader is a reader that can read a C-style NULL terminated string. diff --git a/efi/preinstall/check_pcr4_test.go b/efi/preinstall/check_pcr4_test.go index 5750f9d1..05d7aa48 100644 --- a/efi/preinstall/check_pcr4_test.go +++ b/efi/preinstall/check_pcr4_test.go @@ -475,44 +475,6 @@ func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNotActive(c *C) { c.Check(err, ErrorMatches, `boot option is not active`) } -type mockImageReader struct { - contents []byte - digest tpm2.Digest - closed bool -} - -func (r *mockImageReader) ReadAt(data []byte, offset int64) (int, error) { - copy(data, r.contents[offset:]) - return len(data), nil -} - -func (r *mockImageReader) Close() error { - if r.closed { - return errors.New("already closed") - } - r.closed = true - return nil -} - -func (r *mockImageReader) Size() int64 { - return int64(len(r.contents)) -} - -type mockImage struct { - contents []byte // Used to produce a flat-file digest - digest tpm2.Digest // Authenticode digest -} - -func (i *mockImage) String() string { - return "mock image" -} - -func (i *mockImage) Open() (secboot_efi.ImageReader, error) { - return &mockImageReader{ - contents: i.contents, - digest: i.digest}, nil -} - type testCheckBootManagerCodeMeasurementsParams struct { env internal_efi.HostEnvironment pcrAlg tpm2.HashAlgorithmId diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go new file mode 100644 index 00000000..b4e35f0e --- /dev/null +++ b/efi/preinstall/check_pcr7.go @@ -0,0 +1,529 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall + +import ( + "bytes" + "context" + "crypto/rsa" + "crypto/x509" + "errors" + "fmt" + "io" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" + "github.com/canonical/tcglog-parser" + secboot_efi "github.com/snapcore/secboot/efi" + internal_efi "github.com/snapcore/secboot/internal/efi" + pe "github.com/snapcore/secboot/internal/pe1.14" +) + +var ( + internal_efiSecureBootSignaturesFromPEFile = internal_efi.SecureBootSignaturesFromPEFile + peNewFile = pe.NewFile +) + +type secureBootPolicyResultFlags int + +const ( + secureBootIncludesWeakAlg secureBootPolicyResultFlags = 1 << iota + secureBootPreOSVerificationIncludesDigest +) + +type secureBootPolicyResult struct { + UsedAuthorities []*x509.Certificate + Flags secureBootPolicyResultFlags +} + +func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, iblImage secboot_efi.Image) (result *secureBootPolicyResult, err error) { + if iblImage == nil { + return nil, errors.New("must supply the initial boot loader image") + } + + varCtx := env.VarContext(ctx) + + // Make sure that secure boot is enabled - we don't generate PCR7 policies for systems + // without secure boot enabled. + secureBoot, err := efi.ReadSecureBootVariable(varCtx) + if err != nil { + return nil, fmt.Errorf("cannot read SecureBoot variable: %w", err) + } + if !secureBoot { + // WithSecureBootPolicyProfile() doesn't generate working profiles if secure + // boot is disabled. + return nil, ErrNoSecureBoot + } + + // On UEFI 2.5 and later, we require that deployed mode is enabled, because if it's disabled, it + // changes the sequence of events for PCR7 (the DeployedMode and AuditMode global variables are + // also measured). + // TODO(chrisccoulson): relax this later on in the profile generation to support user mode, but + // maybe add a new flag (RequireDeployedMode or AllowUserMode) to RunChecks. We should be + // able to generate policies for user mode as well - it shouldn't be necessary to enable deployed + // mode as long as secure boot is enabled, particularly because the only paths back from deployed + // mode are platform specific (ie, it could be a one way operation!) + if efi.IsDeployedModeSupported(varCtx) { + secureBootMode, err := efi.ComputeSecureBootMode(varCtx) + if err != nil { + return nil, fmt.Errorf("cannot compute secure boot mode: %w", err) + } + if secureBootMode != efi.DeployedMode { + // WithSecureBootPolicyProfile() doesn't generate working profiles if deployed mode is not + // enabled on UEFI >= 2.5. + return nil, ErrNoDeployedMode + } + } + + // Make sure this system doesn't support features that affect PCR7 and which we don't + // currently support. + osIndicationsSupported, err := efi.ReadOSIndicationsSupportedVariable(varCtx) + if err != nil { + return nil, fmt.Errorf("cannot read OsIndicationsSupported variable: %w", err) + } + if osIndicationsSupported&efi.OSIndicationTimestampRevocation > 0 { + // Timestamp verification relies on another database (dbt) which we currently don't support + // in WithSecureBootPolicyProfile(). It's theoretically possible we might see this in the + // wild and might have to add support for it in the future. + return nil, errors.New("generating secure boot profiles for systems with timestamp revocation (dbt) support is currently not supported") + } + if osIndicationsSupported&efi.OSIndicationStartOSRecovery > 0 { + // OS recovery relies on another database (dbr) which we currently don't support in + // WithSecureBootPolicyProfile(), but given this also depends on EFI_VARIABLE_AUTHENTICATION_3, + // it's unlikely we'll ever see this in the wild. + return nil, errors.New("generating secure boot profiles for systems with OS recovery support, which requires dbr support, is not supported") + } + // TODO(chrisccoulson): Not sure if there's any indication that we might get SPDM related measurements, + // which our profile generation for PCR7 currently doesn't support. + + // Obtain the BootCurrent variable and use this to obtain the corresponding load option + // that was measured to the log. BootXXXX variables are measured to the TPM and so we don't + // need to read back from an EFI variable that could have been modified between boot time + // and now. We need this so that we can identify the launch of the initial boot loader later + // on. This uses the same code that we use for PCR4 checks. + current, err := efi.ReadBootCurrentVariable(varCtx) + if err != nil { + return nil, fmt.Errorf("cannot read BootCurrent variable: %w", err) + } + bootOpt, err := readLoadOptionFromLog(log, current) + if err != nil { + return nil, fmt.Errorf("cannot read current Boot%04x load option from log: %w", current, err) + } + + // Make sure that the secure boot config in the log is measured in the + // expected order, else WithSecureBootPolicyProfile() will generate an invalid policy, + // because we hard code the order. The order here is what we expect to see. + configs := []efi.VariableDescriptor{ + {Name: "SecureBoot", GUID: efi.GlobalVariable}, + {Name: "PK", GUID: efi.GlobalVariable}, + {Name: "KEK", GUID: efi.GlobalVariable}, + {Name: "db", GUID: efi.ImageSecurityDatabaseGuid}, + {Name: "dbx", GUID: efi.ImageSecurityDatabaseGuid}, + // TODO: Add optional dbt / SPDM in the future. + } + + result = new(secureBootPolicyResult) + var ( + db efi.SignatureDatabase // The authorized signature database from the TCG log. + measuredSignatures tpm2.DigestList // The verification event digests measured by the firmware + seenOSPresentVerification bool // Whether we've seen a verification event in the OS-present phase + seenIBLLoadEvent bool // Whether we've seen the launch event for the OS initial boot loader + ) + + phaseTracker := newTcgLogPhaseTracker() +NextEvent: + for _, ev := range log.Events { + phase, err := phaseTracker.processEvent(ev) + if err != nil { + return nil, err + } + + switch phase { + case tcglogPhasePreOSMeasuringSecureBootConfig: + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + // Not PCR7 + continue NextEvent + } + + switch ev.EventType { + case tcglog.EventTypeEFIVariableDriverConfig: + if len(configs) == 0 { + // Unexpected config event - we're not expecting another secure boot variable + // to measure. We should have exitted the loop by now. + return nil, errors.New("unexpected EV_EFI_VARIABLE_DRIVER_CONFIG event: all expected secure boot variable have been measured") + } + + // Pop the next secure boot config name + config := configs[0] + configs = configs[1:] + + data, ok := ev.Data.(*tcglog.EFIVariableData) + if !ok { + // The data resulting from decode errors are guaranteed to implement the error interface + return nil, fmt.Errorf("invalid event data for EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", ev.Data.(error)) + } + // Make sure this is the event we're expecting to be measured. If they're + // measured in an unexpected order, then WithSecureBootPolicyProfile() will + // generate an invalid policy. + if data.VariableName != config.GUID || data.UnicodeName != config.Name { + return nil, fmt.Errorf("unexpected EV_EFI_VARIABLE_DRIVER_CONFIG event ordering (expected %s-%v, got %s-%v)", + config.Name, config.GUID, data.UnicodeName, data.VariableName) + } + + // Compute the expected digest from the event data in the log and make + // sure it's consistent with the measured digest. + expectedDigest := tcglog.ComputeEFIVariableDataDigest(pcrAlg.GetHash(), data.UnicodeName, data.VariableName, data.VariableData) + if !bytes.Equal(ev.Digests[pcrAlg], expectedDigest) { + return nil, fmt.Errorf("event data inconsistent with measured digest for EV_EFI_VARIABLE_DRIVER_CONFIG event (name:%q, GUID:%v, expected digest:%#x, measured digest:%#x)", + data.UnicodeName, data.VariableName, expectedDigest, ev.Digests[pcrAlg]) + } + + switch data.UnicodeName { + case "SecureBoot": + // Make sure the SecureBoot value in the log matches the EFI variable, + // (ie, []byte{1}). We don't do this for other variables because they can + // be updated from the OS, making them potentially inconsistent. The + // SecureBoot variable is read only after ExitBootServices. + if !bytes.Equal(data.VariableData, []byte{1}) { + return nil, errors.New("SecureBoot variable is not consistent with the corresponding EV_EFI_VARIABLE_DRIVER_CONFIG event value in the TCG log") + } + case "PK": + // Make sure that we can parse the PK database and it contains a single + // X.509 entry. + pk, err := efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) + if err != nil { + return nil, fmt.Errorf("cannot decode PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event data: %w", err) + } + switch len(pk) { + case 0: + // This should never be empty when secure boot is enabled, + // so if it does then the firmware is broken. + return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: no signature list when secure boot is enabled") + case 1: + esl := pk[0] + if esl.Type != efi.CertX509Guid { + // PK can only contain a X.509 certificate. If we get another + // type then the firmwar is broken. + return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list has an unexpected type: %v", esl.Type) + } + if len(esl.Signatures) != 1 { + // EFI_CERT_X509_GUID signature lists can only contain a single + // signature. If there isn't then the firmware is broken. + return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list should only have one signature, but got %d", len(esl.Signatures)) + } + if _, err := x509.ParseCertificate(esl.Signatures[0].Data); err != nil { + return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: cannot decode PK certificate: %w", err) + } + default: + // If PK contains more than 1 ESL, then the firmware is broken. + return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: more than one signature list is present") + } + case "db": + // Capture the db from the log for future use. + var err error + db, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) + if err != nil { + return nil, fmt.Errorf("cannot decode db contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", err) + } + // We don't check the EFI_SIGNATURE_LIST types contained in db. Any OS component with a valid + // Authenticode signature (WIN_CERT_TYPE_PKCS_SIGNED_DATA) or a valid PKCS7 signature + // (WIN_CERT_TYPE_EFI_GUID with the type EFI_CERT_TYPE_PKCS7_GUID) is authenticated with + // signature verification using the matching EFI_CERT_X509_GUID entry first, and the profile + // generation in secboot efi makes sure a signed binary has a matching root in the relevant + // trust stores. The presence of digest signatures (EFI_CERT_SHA*) shouldn't matter because + // these are only used to authenticate unsigned images (which the profile generation in the + // secboot efi package rejects) or as a fallback for signed image where signature verification + // fails. Digests may be permitted for authenticating unsigned pre-OS components. + default: + // Make sure that we can parse all other signature databases ok + if _, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)); err != nil { + return nil, fmt.Errorf("cannot decode %s contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", data.UnicodeName, err) + } + } + case tcglog.EventTypeEFIAction: + // This branch exists here for documentation purposes - it falls through to the + // default branch below, which returns an error. + // + // An EV_EFI_ACTION events with the string "UEFI Debug Mode" appears at the + // start of the log if a debugging endpoint is enabled. It's also possible that + // EV_EFI_ACTION events are used for other conditions in PCR7 that weaken device + // security (eg, the "DMA Protection Disabled" event). + // + // In general, it's not normal to see EV_EFI_ACTION events and these indicate some + // sort of abnormal condition that has a detrimental effect on device security. + // WithSecureBootPolicyProfile() will generate an invalid policy in this case because + // it doesn't emit them. + // + // Just return an error here to prevent the use of WithSecureBootPolicyProfile(). The + // "UEFI Debug Mode" and "DMA Protection Disabled" cases are already picked up by the + // firmware protection checks, so we don't need any special handling here. + fallthrough + default: + // Anything that isn't EV_EFI_VARIABLE_DRIVER_CONFIG ends up here. + return nil, fmt.Errorf("unexpected %v event %q whilst measuring config", ev.EventType, ev.Data) + } + case tcglogPhasePreOSAfterMeasureSecureBootConfig, tcglogPhaseOSPresent: + if len(configs) > 0 { + // We've transitioned to a phase where components can be loaded and verified but we haven't + // measured all of the secure boot variables. We'll fail to generate a valid policy with + // WithSecureBootPolicyProfile() in this case. + return nil, errors.New("EV_EFI_VARIABLE_DRIVER_CONFIG events for some secure boot variables missing from log") + } + + if ev.PCRIndex == internal_efi.BootManagerCodePCR && + ev.EventType == tcglog.EventTypeEFIBootServicesApplication && + phase == tcglogPhaseOSPresent && + !seenIBLLoadEvent { + // This is an EV_EFI_BOOT_SERVICES_APPLICATION event during OS-present, + // and we haven't seen the event for the IBL yet. We stop once we see this + // because at this point, the rest of the measurements in this PCR are under + // the control of the OS. + data, ok := ev.Data.(*tcglog.EFIImageLoadEvent) + if !ok { + return nil, fmt.Errorf("invalid OS-present EV_EFI_BOOT_SERVICES_APPLICATION event data: %w", ev.Data.(error)) + } + + yes, err := isLaunchedFromLoadOption(ev, bootOpt) + if err != nil { + return nil, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is associated with the current boot load option: %w", data.DevicePath, err) + } + if !yes { + // This is not the launch event for the initial boot loader - ignore it. + if seenOSPresentVerification { + // The way we build profiles for PCR7 requires that any verification + // events in PCR7 during OS-present to be associated with the OS. If + // we've seen a verification event and we're in OS-present, then the + // next expected event is the load event for the initial boot loader. + // If we get verification events for Absolute (which is loaded from + // Flash and is normally verified earlier on with the verification of + // other Flash volumes), then we'll generate a potentially invalid + // profile for PCR7, because we don't copy events from the log once + // we're in OS-present. + return nil, fmt.Errorf("unexpected EV_EFI_BOOT_SERVICES_APPLICATION event for %v after already seeing a verification event during the OS-present environment. This event should be for the initial boot loader", data.DevicePath) + } + continue NextEvent + } + + // This is the IBL for the OS. Obtain signatures from binary + seenIBLLoadEvent = true + sigs, err := func() ([]*efi.WinCertificateAuthenticode, error) { + r, err := iblImage.Open() + if err != nil { + return nil, fmt.Errorf("cannot open image: %w", err) + } + defer r.Close() + + pefile, err := peNewFile(r) + if err != nil { + return nil, fmt.Errorf("cannot decode image: %w", err) + } + + return internal_efiSecureBootSignaturesFromPEFile(pefile, r) + }() + if err != nil { + return nil, fmt.Errorf("cannot obtain secure boot signatures from image %s: %w", iblImage, err) + } + + // Make sure that one of the CA's used for verification so far + // is a trust anchor for one of the signatures on the image. + var foundSig *efi.WinCertificateAuthenticode + for _, cert := range result.UsedAuthorities { + for _, sig := range sigs { + if sig.CertLikelyTrustAnchor(cert) { + foundSig = sig + break + } + } + if foundSig != nil { + break + } + } + if foundSig == nil { + return nil, errors.New("OS initial boot loader was not verified by any X.509 certificate measured by any EV_EFI_VARIABLE_AUTHORITY event") + } + + signer := foundSig.GetSigner() + switch signer.PublicKeyAlgorithm { + case x509.RSA: + pubKey, ok := signer.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errors.New("signer certificate for OS initial boot loader contains unsupported public key type") + } + if pubKey.N.BitLen() <= 1024 { + result.Flags |= secureBootIncludesWeakAlg + } + default: + return nil, errors.New("signer certificate for OS initial boot loader contains unsupported public key algorithm") + } + + // This is the launch of the IBL. At this point, events are under control of the + // OS, so we stop checking, even though we may miss some events created by + // firmware via the LoadImage API. + break NextEvent + } + + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + // Not PCR7 + continue NextEvent + } + + switch ev.EventType { + case tcglog.EventTypeEFIVariableAuthority: + // Decode the verification event + data, ok := ev.Data.(*tcglog.EFIVariableData) + if !ok { + // if decoding failed, the resulting data is guaranteed to implement error. + return nil, fmt.Errorf("EV_EFI_VARIABLE_AUTHORITY event has wrong data format: %w", ev.Data.(error)) + } + + // As we're only checking events up to the launch of the IBL, we don't expect + // to see anything other than verification events from db here. + if data.VariableName != efi.ImageSecurityDatabaseGuid || data.UnicodeName != "db" { + return nil, fmt.Errorf("EV_EFI_VARIABLE_AUTHORITY event is not from db (got %s-%v)", data.UnicodeName, data.VariableName) + } + + if phase == tcglogPhaseOSPresent { + // Compute the expected digest from the event data in the log and make + // sure it's consistent with the measured digest. We only do this for + // OS-present events because these are the ones we compute. Pre-OS + // events are just copied from the log. + seenOSPresentVerification = true + expectedDigest := tcglog.ComputeEFIVariableDataDigest(pcrAlg.GetHash(), data.UnicodeName, data.VariableName, data.VariableData) + if !bytes.Equal(ev.Digests[pcrAlg], expectedDigest) { + return nil, fmt.Errorf("event data inconsistent with %v event digest for EV_EFI_VARIABLE_AUTHORITY event (log digest:%#x, expected digest:%#x)", pcrAlg, ev.Digests[pcrAlg], expectedDigest) + } + } + + // Make sure that this signature hasn't already been measured. Duplicate signatures measured + // by the firmware may result in incorrectly computed PCR policies. + // Unfortunately, this test isn't 100% reliable as we stop processing events after the launch + // of the IBL (usually shim). Once the IBL has launched, we can't tell whether subsequent events + // were generated by the firmware because an OS component made use of LoadImage (where we would + // want to make sure it isn't measured again) or whether subsequent events are measured via some + // other mechanism by an OS component, such as the shim verification (which we wouldn't want to + // check, because we're only testing firmware compatbility here). I can't think of a way to make + // this 100% reliable other than by ensuring OS components never measure events with "db" and + // IMAGE_SECURITY_DATABASE_GUID in their event data, as a way of being able to distinguish + // firmware generated events from OS component generated events. It's a legitimate scenario for + // both the firmware and shim to both measure the same signature they used for verification from db + // because they both maintain their own de-duplication lists. + // + // If this test fails, the firmware is definitely broken. If this test doesn't fail, the opposite is + // not true - it's not a definitive guarantee that the firmware isn't broken, unfortunately. + for _, measured := range measuredSignatures { + if bytes.Equal(measured, ev.Digests[pcrAlg]) { + return nil, fmt.Errorf("EV_EFI_VARIABLE_AUTHORITY digest %#x has been measured by the firmware more than once", ev.Digests[pcrAlg]) + } + } + measuredSignatures = append(measuredSignatures, ev.Digests[pcrAlg]) + + // Try to discover the type of authentication. The measured EFI_SIGNATURE_DATA doesn't + // contain this. + + // First of all, construct a signature data entry from the raw event data. + esd := new(efi.SignatureData) + r := bytes.NewReader(data.VariableData) + + // THE EFI_SIGNATURE_DATA entry starts with the owner GUID + sigOwner, err := efi.ReadGUID(r) + if err != nil { + return nil, fmt.Errorf("cannot decode owner GUID from EV_EFI_VARIABLE_AUTHORITY event: %w", err) + } + esd.Owner = sigOwner + + // The rest of the EFI_SIGNATURE_DATA entry is the data + sigData, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("cannot read data from EV_EFI_VARIABLE_AUTHORITY event: %w", err) + } + esd.Data = sigData + + // We have a fully constructed EFI_SIGNATURE_DATA. Now iterate over db to see if this + // EFI_SIGNATURE_DATA belongs to any EFI_SIGNATURE_LIST, in order to grab its type. + var matchedEsl *efi.SignatureList + for _, list := range db { + for _, sig := range list.Signatures { + if sig.Equal(esd) { + matchedEsl = list + break + } + } + if matchedEsl != nil { + break + } + } + if matchedEsl == nil { + return nil, fmt.Errorf("encountered db EV_EFI_VARIABLE_AUTHORITY event with data that doesn't match to any db EFI_SIGNATURE_LIST") + } + + switch matchedEsl.Type { + case efi.CertX509Guid: + cert, err := x509.ParseCertificate(esd.Data) + if err != nil { + return nil, fmt.Errorf("cannot decode X.509 certificate from db EV_EFI_VARIABLE_AUTHORITY event: %w", err) + } + result.UsedAuthorities = append(result.UsedAuthorities, cert) + + switch cert.PublicKeyAlgorithm { + case x509.RSA: + pubKey, ok := cert.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errors.New("db EV_EFI_VARIABLE_AUTHORITY event includes X.509 certificate with unsupported public key type") + } + if pubKey.N.BitLen() <= 1024 { + result.Flags |= secureBootIncludesWeakAlg + } + default: + return nil, errors.New("db EV_EFI_VARIABLE_AUTHORITY event includes X.509 certificate with unsupported public key algorithm") + } + // XXX: unfortunately the verification event only includes the CA certificate - it's not possible from this to + // determine the actual signing certificate, it's signature algorithm, and the algorithm used for signing the + // binary. + case efi.CertSHA1Guid: + // Hopefully there shouldn't be any components being authenticated by a digest. We don't support this for + // OS components so this might be relevant for pre-OS, but it would make PCR7 incredibly fragile. + result.Flags |= secureBootIncludesWeakAlg + fallthrough + case efi.CertSHA224Guid, efi.CertSHA256Guid, efi.CertSHA384Guid, efi.CertSHA512Guid: + if phase == tcglogPhaseOSPresent { + return nil, errors.New("encountered EV_EFI_VARIABLE_AUTHORITY event without X.509 certificate during OS present, which is not supported") + } + result.Flags |= secureBootPreOSVerificationIncludesDigest + default: + return nil, fmt.Errorf("unrecognized EFI_SIGNATURE_DATA type for EV_EFI_VARIABLE_AUTHORITY event: %v", matchedEsl.Type) + } + case tcglog.EventTypeSeparator: + // ok + default: + // Anything that isn't EV_EFI_VARIABLE_AUTHORITY ends up here. + return nil, fmt.Errorf("unexpected %v event %q whilst measuring verification", ev.EventType, ev.Data) + } + case tcglogPhasePreOSAfterMeasureSecureBootConfigUnterminated: + if ev.PCRIndex == internal_efi.SecureBootPolicyPCR { + return nil, fmt.Errorf("unexpected %v event in PCR7 after measuring config but before transitioning to OS-present", ev.EventType) + } + } + } + + if !seenIBLLoadEvent { + return nil, errors.New("missing load event for initial boot loader") + } + return result, nil +} diff --git a/efi/preinstall/check_pcr7_test.go b/efi/preinstall/check_pcr7_test.go new file mode 100644 index 00000000..02aea183 --- /dev/null +++ b/efi/preinstall/check_pcr7_test.go @@ -0,0 +1,1120 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall_test + +import ( + "context" + "crypto" + "crypto/x509" + "errors" + "io" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" + "github.com/canonical/tcglog-parser" + secboot_efi "github.com/snapcore/secboot/efi" + . "github.com/snapcore/secboot/efi/preinstall" + internal_efi "github.com/snapcore/secboot/internal/efi" + "github.com/snapcore/secboot/internal/efitest" + pe "github.com/snapcore/secboot/internal/pe1.14" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +// TODO: It might be good to move the functionality in efi/vars_test.go to internal/efitest and +// add some extra functionality in order to simplify some of the setup here, but that's for +// another PR. +// +// It will be especially useful if I add an option in a future PR to compare the signature database +// contents in the log with the current signature database contents read from the variables, in the +// scenario when we can be sure they haven't been modified from the OS. The first iteration of this +// code does not compare them yet (with the exception of the SecureBoot variable, which is read only), +// so there's no need to set up a full signature database configuration for each test. + +type pcr7Suite struct{} + +var _ = Suite(&pcr7Suite{}) + +type testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams struct { + env internal_efi.HostEnvironment + pcrAlg tpm2.HashAlgorithmId + iblImage secboot_efi.Image + + expectedFlags SecureBootPolicyResultFlags + expectedUsedAuthorities []*x509.Certificate +} + +func (s *pcr7Suite) testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c *C, params *testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams) error { + var ( + expectedImageReader io.ReaderAt + expectedPeFile *pe.File + ) + restore := MockPeNewFile(func(r io.ReaderAt) (*pe.File, error) { + c.Check(r, NotNil) + expectedImageReader = r + expectedPeFile = new(pe.File) + return expectedPeFile, nil + }) + defer restore() + + restore = MockInternalEfiSecureBootSignaturesFromPEFile(func(pefile *pe.File, r io.ReaderAt) ([]*efi.WinCertificateAuthenticode, error) { + c.Check(pefile, Equals, expectedPeFile) + c.Check(r, Equals, expectedImageReader) + c.Assert(r, testutil.ConvertibleTo, &mockImageReader{}) + imageReader := r.(*mockImageReader) + return imageReader.signatures, nil + }) + defer restore() + + log, err := params.env.ReadEventLog() + c.Assert(err, IsNil) + + result, err := CheckSecureBootPolicyMeasurementsAndObtainAuthorities(context.Background(), params.env, log, params.pcrAlg, params.iblImage) + if err != nil { + return err + } + c.Check(result.Flags, Equals, params.expectedFlags) + c.Assert(result.UsedAuthorities, HasLen, len(params.expectedUsedAuthorities)) + for i, authority := range result.UsedAuthorities { + c.Check(authority.Equal(params.expectedUsedAuthorities[i]), testutil.IsTrue) + } + return nil +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGood(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPolicyResultFlags(0), + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodSHA384(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA384, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPolicyResultFlags(0), + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithDriverLaunch(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeDriverLaunch: true, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPolicyResultFlags(0), + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithDriverLaunchVerifiedByDigest(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeDriverLaunch: true, + PreOSVerificationUsesDigests: crypto.SHA256, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPreOSVerificationIncludesDigest, + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithDriverLaunchVerifiedByDigestSHA1(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeDriverLaunch: true, + PreOSVerificationUsesDigests: crypto.SHA1, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootIncludesWeakAlg | SecureBootPreOSVerificationIncludesDigest, + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithSysPrepLaunch(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeSysPrepAppLaunch: true, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPolicyResultFlags(0), + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithAbsoluteLaunch(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d}), + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPolicyResultFlags(0), + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodPreUEFI25(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + expectedFlags: SecureBootPolicyResultFlags(0), + expectedUsedAuthorities: []*x509.Certificate{ + testutil.ParseCertificate(c, msUefiCACert), + }, + }) + c.Check(err, IsNil) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadNoSecureBoot(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, Equals, ErrNoSecureBoot) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadUserMode(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, Equals, ErrNoDeployedMode) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadFWSupportsDBT(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `generating secure boot profiles for systems with timestamp revocation \(dbt\) support is currently not supported`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadFWSupportsDBR(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `generating secure boot profiles for systems with OS recovery support, which requires dbr support, is not supported`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadNoBootCurrent(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `cannot read BootCurrent variable: variable does not exist`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadNoLoadOption(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x4, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `cannot read current Boot0004 load option from log: cannot find specified boot option`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadUnexpectedConfigMeasurement(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + var eventsCopy []*tcglog.Event + for _, ev := range log.Events { + eventsCopy = append(eventsCopy, ev) + + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableDriverConfig { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName == "dbx" { + // Measure dbx twice + eventsCopy = append(eventsCopy, ev) + } + } + log.Events = eventsCopy + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `unexpected EV_EFI_VARIABLE_DRIVER_CONFIG event: all expected secure boot variable have been measured`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadConfigEventDataErr(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableDriverConfig { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName == "dbx" { + ev.Data = &invalidEventData{errors.New("some error")} + } + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `invalid event data for EV_EFI_VARIABLE_DRIVER_CONFIG event: some error`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidConfigMeasurementOrder(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + var eventsCopy []*tcglog.Event + for _, ev := range log.Events { + eventsCopy = append(eventsCopy, ev) + + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableDriverConfig { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName == "dbx" { + // Swap db and dbx measurements + dbx := eventsCopy[len(eventsCopy)-1] + eventsCopy[len(eventsCopy)-1] = eventsCopy[len(eventsCopy)-2] + eventsCopy[len(eventsCopy)-2] = dbx + } + } + log.Events = eventsCopy + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `unexpected EV_EFI_VARIABLE_DRIVER_CONFIG event ordering \(expected db-d719b2cb-3d3a-4596-a3bc-dad00e67656f, got dbx-d719b2cb-3d3a-4596-a3bc-dad00e67656f\)`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadConfigDigestWrong(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableDriverConfig { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName == "dbx" { + ev.Digests[tpm2.HashAlgorithmSHA256] = testutil.DecodeHexString(c, "8c8d89cdf0f2de4a1e97d436d7f6a19c49ab55d33bdb81c27470d4140b3de220") + } + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `event data inconsistent with measured digest for EV_EFI_VARIABLE_DRIVER_CONFIG event \(name:\"dbx\", GUID:d719b2cb-3d3a-4596-a3bc-dad00e67656f, expected digest:0x1963d580fcc0cede165e23837b55335eebe18750c0b795883386026ea071e3c6, measured digest:0x8c8d89cdf0f2de4a1e97d436d7f6a19c49ab55d33bdb81c27470d4140b3de220\)`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidSecureBootValueInLog(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableDriverConfig { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName == "SecureBoot" { + data.VariableData = []byte{0} + ev.Digests[tpm2.HashAlgorithmSHA256] = testutil.DecodeHexString(c, "115aa827dbccfb44d216ad9ecfda56bdea620b860a94bed5b7a27bba1c4d02d8") + } + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `SecureBoot variable is not consistent with the corresponding EV_EFI_VARIABLE_DRIVER_CONFIG event value in the TCG log`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadUEFIDebuggerPresent(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + FirmwareDebugger: true, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `unexpected EV_EFI_ACTION event \"UEFI Debug Mode\" whilst measuring config`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadMissingConfigMeasurement(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + var eventsCopy []*tcglog.Event + for _, ev := range log.Events { + eventsCopy = append(eventsCopy, ev) + + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableDriverConfig { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName == "dbx" { + // Delete dbx measurement + eventsCopy = eventsCopy[:len(eventsCopy)-1] + } + } + log.Events = eventsCopy + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_DRIVER_CONFIG events for some secure boot variables missing from log`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadMissingIBLLaunch(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + var eventsCopy []*tcglog.Event + seenIBLVerification := false + for _, ev := range log.Events { + eventsCopy = append(eventsCopy, ev) + + switch { + case ev.PCRIndex == internal_efi.BootManagerCodePCR && ev.EventType == tcglog.EventTypeEFIBootServicesApplication: + // Delete measurement + eventsCopy = eventsCopy[:len(eventsCopy)-1] + case ev.PCRIndex == internal_efi.SecureBootPolicyPCR && ev.EventType == tcglog.EventTypeEFIVariableAuthority && !seenIBLVerification: + seenIBLVerification = true + case ev.PCRIndex == internal_efi.SecureBootPolicyPCR && ev.EventType == tcglog.EventTypeEFIVariableAuthority && seenIBLVerification: + // Delete measurement + eventsCopy = eventsCopy[:len(eventsCopy)-1] + } + } + log.Events = eventsCopy + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `missing load event for initial boot loader`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadFirstVerifiedOSPresentLoadIsntIBL(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `unexpected EV_EFI_BOOT_SERVICES_APPLICATION event for \\PciRoot\(0x0\)\\Pci\(0x1d,0x0\)\\Pci\(0x0,0x0\)\\NVMe\(0x1,00-00-00-00-00-00-00-00\)\\HD\(1,GPT,66de947b-fdb2-4525-b752-30d66bb2b960\)\\\\EFI\\ubuntu\\shimx64.efi after already seeing a verification event during the OS-present environment. This event should be for the initial boot loader`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidSourceForFirstOSPresentVerification(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableAuthority { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + data.VariableName = efi.GlobalVariable + break + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_AUTHORITY event is not from db \(got db-8be4df61-93ca-11d2-aa0d-00e098032b8c\)`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadFirstOSPresentVerificationWrongDigest(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableAuthority { + continue + } + ev.Digests[tpm2.HashAlgorithmSHA256] = testutil.DecodeHexString(c, "8c8d89cdf0f2de4a1e97d436d7f6a19c49ab55d33bdb81c27470d4140b3de220") + break + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `event data inconsistent with TPM_ALG_SHA256 event digest for EV_EFI_VARIABLE_AUTHORITY event \(log digest:0x8c8d89cdf0f2de4a1e97d436d7f6a19c49ab55d33bdb81c27470d4140b3de220, expected digest:0x4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9\)`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadDuplicateVerificationDigests(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeDriverLaunch: true, + }) + var ( + eventsCopy []*tcglog.Event + verificationEvent *tcglog.Event + seenIBLLaunch bool + ) + for _, ev := range log.Events { + eventsCopy = append(eventsCopy, ev) + + switch { + case ev.PCRIndex == internal_efi.SecureBootPolicyPCR && ev.EventType == tcglog.EventTypeEFIVariableAuthority: + // This should be the only verification event. Save it to put it in the log later on + verificationEvent = ev + case ev.PCRIndex == internal_efi.BootManagerCodePCR && ev.EventType == tcglog.EventTypeEFIBootServicesApplication && !seenIBLLaunch: + seenIBLLaunch = true + // Copy the previous verification event before this event + eventsCopy = append(eventsCopy, ev) + eventsCopy[len(eventsCopy)-2] = verificationEvent + } + } + log.Events = eventsCopy + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_AUTHORITY digest 0x4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9 has been measured by the firmware more than once`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidIBLLoadEventData(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.BootManagerCodePCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { + continue + } + ev.Data = &invalidEventData{errors.New("some error")} + break + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `invalid OS-present EV_EFI_BOOT_SERVICES_APPLICATION event data: some error`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidVerificationEventData(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableAuthority { + continue + } + ev.Data = &invalidEventData{errors.New("some error")} + break + } + + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(log), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_AUTHORITY event has wrong data format: some error`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadDMAProtectionDisabled(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + DMAProtectionDisabled: efitest.DMAProtectionDisabledNullTerminated, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `unexpected EV_EFI_ACTION event \"DMA Protection Disabled\" whilst measuring verification`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadMissingSecureBoot(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `cannot read SecureBoot variable: variable does not exist`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidSecureBootMode(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `cannot compute secure boot mode: inconsistent secure boot mode: firmware indicates audit mode is enabled when not in setup mode`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadMissingOsIndications(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + iblImage: &mockImage{ + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + }) + c.Check(err, ErrorMatches, `cannot read OsIndicationsSupported variable: variable does not exist`) +} + +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadMissingIBL(c *C) { + err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + }) + c.Check(err, ErrorMatches, `must supply the initial boot loader image`) +} + +// TODO (some harder, and some of these may require a more customizable test log generation in internal/efitest/log.go): +// - Various PK/KEK/db/dbx decoding errors +// - Failure to open initial boot loader image +// - Failure to decode initial boot loader image +// - Initial boot loader has signature that doesn't chain to anything in db +// - Initial boot loader signer does not have a RSA key +// - Initial boot loader signer has a weak RSA key +// - EV_EFI_VARIABLE_AUTHORITY event has ESD that doesn't match any ESL in db +// - EV_EFI_VARIABLE_AUTHORITY event has ESD that contains a non RSA key +// - EV_EFI_VARIABLE_AUTHORITY event has ESD with weak RSA key +// - EV_EFI_VARIABLE_AUTHORITY containing a digest in OS-present +// - EV_EFI_VARIABLE_AUTHORITY event has ESD with unrecognized ESL type +// - Spurious events in PCR7 after measuring config with logs that don't terminate the config with a EV_SEPARATOR diff --git a/efi/preinstall/check_tcglog.go b/efi/preinstall/check_tcglog.go index 6e10ddda..70c8b1b6 100644 --- a/efi/preinstall/check_tcglog.go +++ b/efi/preinstall/check_tcglog.go @@ -519,12 +519,18 @@ func (t *tcglogPhaseTracker) processEvent(ev *tcglog.Event) (phase tcglogPhase, // No change for any other event to PCR7 } case t.phase == tcglogPhasePreOSAfterMeasureSecureBootConfigUnterminated: + // XXX(chrisccoulson): It's not clear whether this should return to + // tcglogPhasePreOSMeasuringSecureBootConfig if there is an event in PCR7. The + // justification for this is that PCR7 should begin with an extra event indicating + // that there is a debugger active if that is the case. EDK2 measures this in the + // same block of events as the secure boot configuration, but we don't know whether + // all firmware does this. The consequence of some firmware measuring it separately + // is that we may end up failing other PCRs unnecessarily for tests that use this + // functionality, because of the logic here. switch { case ev.EventType == tcglog.EventTypeSeparator: // Any EV_SEPARATOR from this phase begins the transition to OS present. t.phase = tcglogPhaseTransitioningToOSPresent - case ev.PCRIndex == internal_efi.SecureBootPolicyPCR: - return 0, errors.New("unexpected event in PCR 7") default: // No change for events in any other PCR. } diff --git a/efi/preinstall/check_tcglog_test.go b/efi/preinstall/check_tcglog_test.go index e907648b..4878012f 100644 --- a/efi/preinstall/check_tcglog_test.go +++ b/efi/preinstall/check_tcglog_test.go @@ -22,7 +22,6 @@ package preinstall_test import ( "crypto" "errors" - "io" "github.com/canonical/go-tpm2" tpm2_testutil "github.com/canonical/go-tpm2/testutil" @@ -758,15 +757,6 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPreOSMeasurementToNonT c.Check(err, ErrorMatches, `measurements were made by firmware from pre-OS environment to non-TCG defined PCR 8`) } -type invalidEventData struct { - err error -} - -func (e *invalidEventData) String() string { return "invalid event data: " + e.err.Error() } -func (*invalidEventData) Bytes() []byte { return nil } -func (*invalidEventData) Write(w io.Writer) error { return errors.New("not supported") } -func (e *invalidEventData) Error() string { return e.err.Error() } - func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSeparatorDecodeError(c *C) { // Test that an error decoding EV_SEPARATOR event data is properly detected s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) @@ -851,53 +841,6 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankUnexpectedSuccessfulSe c.Check(err, ErrorMatches, `unexpected normal EV_SEPARATOR event in PCR 0`) } -func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankUnexpectedEventDuringSecureBootConfigMeasurements(c *C) { - // Test that an unexpected event in PCR7 after measuring the secure boot config but before - // measuring the EV_SEPARATOR results in an error. - s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) - log := efitest.NewLog(c, &efitest.LogOptions{ - Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - DisallowPreOSVerification: true, - }) - var ( - eventsCopy []*tcglog.Event - inSecureBootConfigMeasurement bool - seenSecureBootConfigMeasurement bool - ) - events := log.Events - for len(events) > 0 { - ev := events[0] - events = events[1:] - - eventsCopy = append(eventsCopy, ev) - - switch { - case ev.PCRIndex == internal_efi.SecureBootPolicyPCR && !inSecureBootConfigMeasurement && !seenSecureBootConfigMeasurement: - inSecureBootConfigMeasurement = true - case ev.PCRIndex != internal_efi.SecureBootPolicyPCR && inSecureBootConfigMeasurement && !seenSecureBootConfigMeasurement: - inSecureBootConfigMeasurement = false - seenSecureBootConfigMeasurement = true - - // Add an unexpected event to PCR 7 - eventsCopy = append(eventsCopy, &tcglog.Event{ - PCRIndex: internal_efi.SecureBootPolicyPCR, - EventType: tcglog.EventTypeEFIVariableAuthority, - Data: &invalidEventData{errors.New("some error")}, - Digests: map[tpm2.HashAlgorithmId]tpm2.Digest{ - tpm2.HashAlgorithmSHA256: tcglog.ComputeEventDigest(crypto.SHA256, []byte("foo")), - }, - }) - } - - } - log.Events = eventsCopy - - s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) - - _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, nil) - c.Check(err, ErrorMatches, `unexpected event in PCR 7`) -} - func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankMissingSeparators(c *C) { // Test that an unexpected event amongst the EV_SEPARATORS in PCRs 0-6 is // detected as an error. diff --git a/efi/preinstall/errors.go b/efi/preinstall/errors.go index 3a86014a..b61b2537 100644 --- a/efi/preinstall/errors.go +++ b/efi/preinstall/errors.go @@ -204,3 +204,15 @@ func (e *NoSuitablePCRAlgorithmError) setPcrErrs(results *pcrBankResults) { } e.pcrErrs[results.Alg] = results.pcrErrs() } + +// Errors related to secure boot policy PCR checks. + +var ( + // ErrNoSecureBoot is returned wrapped from DetectSupport to indicate that secure boot is disabled + ErrNoSecureBoot = errors.New("secure boot should be enabled in order to generate secure boot profiles") + + // ErrNoDeployedMode is returned wrapped from DetectSupport to indicate that deployed mode is not + // enabled. In the future, this package will permit generation of profiles on systems that implement + // UEFI >= 2.5 that are in user mode, but this is not the case today. + ErrNoDeployedMode = errors.New("deployed mode should be enabled in order to generate secure boot profiles") +) diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index 4f1863b5..bf4cecb8 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -22,6 +22,9 @@ package preinstall import ( "crypto" "io" + + efi "github.com/canonical/go-efilib" + pe "github.com/snapcore/secboot/internal/pe1.14" ) type ( @@ -31,6 +34,8 @@ type ( CpuVendor = cpuVendor DetectVirtResult = detectVirtResult MeVersion = meVersion + SecureBootPolicyResult = secureBootPolicyResult + SecureBootPolicyResultFlags = secureBootPolicyResultFlags ) const ( @@ -50,25 +55,28 @@ const ( MeFamilyMe = meFamilyMe MeFamilyCsme = meFamilyCsme NoDriversAndAppsPresent = noDriversAndAppsPresent + SecureBootIncludesWeakAlg = secureBootIncludesWeakAlg + SecureBootPreOSVerificationIncludesDigest = secureBootPreOSVerificationIncludesDigest ) var ( - CalculateIntelMEFamily = calculateIntelMEFamily - CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements - CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR - CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements - CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank - CheckForKernelIOMMU = checkForKernelIOMMU - CheckPlatformFirmwareProtections = checkPlatformFirmwareProtections - CheckPlatformFirmwareProtectionsIntelMEI = checkPlatformFirmwareProtectionsIntelMEI - CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings - DetectVirtualization = detectVirtualization - DetermineCPUVendor = determineCPUVendor - IsLaunchedFromLoadOption = isLaunchedFromLoadOption - OpenAndCheckTPM2Device = openAndCheckTPM2Device - ReadIntelHFSTSRegistersFromMEISysfs = readIntelHFSTSRegistersFromMEISysfs - ReadIntelMEVersionFromMEISysfs = readIntelMEVersionFromMEISysfs - ReadLoadOptionFromLog = readLoadOptionFromLog + CalculateIntelMEFamily = calculateIntelMEFamily + CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements + CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR + CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements + CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank + CheckForKernelIOMMU = checkForKernelIOMMU + CheckPlatformFirmwareProtections = checkPlatformFirmwareProtections + CheckPlatformFirmwareProtectionsIntelMEI = checkPlatformFirmwareProtectionsIntelMEI + CheckSecureBootPolicyMeasurementsAndObtainAuthorities = checkSecureBootPolicyMeasurementsAndObtainAuthorities + CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings + DetectVirtualization = detectVirtualization + DetermineCPUVendor = determineCPUVendor + IsLaunchedFromLoadOption = isLaunchedFromLoadOption + OpenAndCheckTPM2Device = openAndCheckTPM2Device + ReadIntelHFSTSRegistersFromMEISysfs = readIntelHFSTSRegistersFromMEISysfs + ReadIntelMEVersionFromMEISysfs = readIntelMEVersionFromMEISysfs + ReadLoadOptionFromLog = readLoadOptionFromLog ) func MockEfiComputePeImageDigest(fn func(crypto.Hash, io.ReaderAt, int64) ([]byte, error)) (restore func()) { @@ -78,3 +86,19 @@ func MockEfiComputePeImageDigest(fn func(crypto.Hash, io.ReaderAt, int64) ([]byt efiComputePeImageDigest = orig } } + +func MockInternalEfiSecureBootSignaturesFromPEFile(fn func(*pe.File, io.ReaderAt) ([]*efi.WinCertificateAuthenticode, error)) (restore func()) { + orig := internal_efiSecureBootSignaturesFromPEFile + internal_efiSecureBootSignaturesFromPEFile = fn + return func() { + internal_efiSecureBootSignaturesFromPEFile = orig + } +} + +func MockPeNewFile(fn func(io.ReaderAt) (*pe.File, error)) (restore func()) { + orig := peNewFile + peNewFile = fn + return func() { + peNewFile = orig + } +} diff --git a/efi/preinstall/preinstall_test.go b/efi/preinstall/preinstall_test.go index 3dd1e3b6..00ab1a16 100644 --- a/efi/preinstall/preinstall_test.go +++ b/efi/preinstall/preinstall_test.go @@ -20,21 +20,98 @@ package preinstall_test import ( + _ "embed" + "errors" "flag" "fmt" + "io" "os" "testing" + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" tpm2_testutil "github.com/canonical/go-tpm2/testutil" + secboot_efi "github.com/snapcore/secboot/efi" + "github.com/snapcore/secboot/internal/testutil" . "gopkg.in/check.v1" ) +var ( + //go:embed testdata/MicrosoftUefiCA.crt + msUefiCACertPEM []byte + + //go:embed testdata/shim-signed_1.54+15.7-0ubuntu1_amd64_latest.pk7 + shimUbuntuSig4PEM []byte + + //go:embed testdata/PkKek-1-snakeoil.pem + snakeoilCertPEM []byte + + dellPKCert []byte + msUefiCACert []byte + shimUbuntuSig4 []byte + snakeoilCert []byte +) + func init() { tpm2_testutil.AddCommandLineFlags() + + msUefiCACert = testutil.MustDecodePEMType("CERTIFICATE", msUefiCACertPEM) + shimUbuntuSig4 = testutil.MustDecodePEMType("PKCS7", shimUbuntuSig4PEM) + snakeoilCert = testutil.MustDecodePEMType("CERTIFICATE", snakeoilCertPEM) } func Test(t *testing.T) { TestingT(t) } +type invalidEventData struct { + err error +} + +func (e *invalidEventData) String() string { return "invalid event data: " + e.err.Error() } +func (*invalidEventData) Bytes() []byte { return nil } +func (*invalidEventData) Write(w io.Writer) error { return errors.New("not supported") } +func (e *invalidEventData) Error() string { return e.err.Error() } + +type mockImageReader struct { + contents []byte + digest tpm2.Digest + signatures []*efi.WinCertificateAuthenticode + closed bool +} + +func (r *mockImageReader) ReadAt(data []byte, offset int64) (int, error) { + copy(data, r.contents[offset:]) + return len(data), nil +} + +func (r *mockImageReader) Close() error { + if r.closed { + return errors.New("already closed") + } + r.closed = true + return nil +} + +func (r *mockImageReader) Size() int64 { + return int64(len(r.contents)) +} + +type mockImage struct { + contents []byte // Used to produce a flat-file digest + digest tpm2.Digest // Authenticode digest + signatures []*efi.WinCertificateAuthenticode +} + +func (i *mockImage) String() string { + return "mock image" +} + +func (i *mockImage) Open() (secboot_efi.ImageReader, error) { + return &mockImageReader{ + contents: i.contents, + digest: i.digest, + signatures: i.signatures}, nil +} + func TestMain(m *testing.M) { // Provide a way for run-tests to configure this in a way that // can be ignored by other suites diff --git a/efi/preinstall/testdata/MicrosoftUefiCA.crt b/efi/preinstall/testdata/MicrosoftUefiCA.crt new file mode 100644 index 00000000..d7c29ef5 --- /dev/null +++ b/efi/preinstall/testdata/MicrosoftUefiCA.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGEDCCA/igAwIBAgIKYQjTxAAAAAAABDANBgkqhkiG9w0BAQsFADCBkTELMAkG +A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE7MDkGA1UEAxMyTWljcm9z +b2Z0IENvcnBvcmF0aW9uIFRoaXJkIFBhcnR5IE1hcmtldHBsYWNlIFJvb3QwHhcN +MTEwNjI3MjEyMjQ1WhcNMjYwNjI3MjEzMjQ1WjCBgTELMAkGA1UEBhMCVVMxEzAR +BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p +Y3Jvc29mdCBDb3Jwb3JhdGlvbjErMCkGA1UEAxMiTWljcm9zb2Z0IENvcnBvcmF0 +aW9uIFVFRkkgQ0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKUIbEzHRQlqSwykwId/BnUMQwFUZOAWfwftkn0LsnO/DArGSkVhoMUWLZbT9Sug ++01Jm0GAkDy5VP3mvNGdxKQYin9BilxZg2gyu4xHye5xvCFPmop8/0Q/jY8ysiZI +rnW17slMHkoZfuSCmh14d00MsL32D9MW07z6K6VROF31+7rbeALb/+wKG5bVg7gZ +E+m2wHtAe+EfKCfJ+u9WXhzmfpR+wPBEsnk55dqyYotNvzhw4mgkFMkzpAg31Vhp +XtN87cEEUwjnTrAqh2MIYW9jFVnqsit51wxhZ4pb/V6th3+6hmdPcVgSIgQiIs6L +71RxAM5QNVh2lQjuarGiAdUCAwEAAaOCAXYwggFyMBIGCSsGAQQBgjcVAQQFAgMB +AAEwIwYJKwYBBAGCNxUCBBYEFPjBa7d/d1NK8yU3HU6hJnsPIHCAMB0GA1UdDgQW +BBQTrb9DCb2CcJyM1U8xbtUimIob1DAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA +QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRFZlJD +4X5YEb/WTp4jVQg7OiJqqDBcBgNVHR8EVTBTMFGgT6BNhktodHRwOi8vY3JsLm1p +Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNDb3JUaGlQYXJNYXJSb29f +MjAxMC0xMC0wNS5jcmwwYAYIKwYBBQUHAQEEVDBSMFAGCCsGAQUFBzAChkRodHRw +Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY0NvclRoaVBhck1hclJv +b18yMDEwLTEwLTA1LmNydDANBgkqhkiG9w0BAQsFAAOCAgEANQhC/zDMzvd2DK0Q +aFg1KUYydid87xJBJ0IbSqptgThIWRNV8+lYNKYWC4KqXa2C2oCDQQaPtB3yA7nz +Gl0b8VCQ+bNVhEIoHCC9sq5RFMXArJeVIRyQ2w/8d56Vc5GIyr29UrkFUA3fV56g +Ye0N5W0l2UAPF0DIzqNKwk2vmhIdCFSPvce8uSs9SSsfMvxqIWlPm8h+QjT8NgYX +i48gQMCzmiV1J83JA6P2XdHnNlR6uVC10xLRB7+7dN/cHo+A1e0Y9C8UFmsv3maM +sCPlx4TY7erBM4KtVksYLfFolQfNz/By8K673YaFmCwhTDMr8A9K8GiHtZJVMnWh +aoJqPKMlEaTtrdcErsvYQFmghNGVTGKRIhp0HYw9Rw5EpuSwmzQ1sfq2U6gsgeyk +BXHInbi66BtEZuRHVA6OVn+znxaYsobQaD6QI7UvXo9QhY3GjYJfQaH0Lg3gmdJs +deS2abUhhvoH0fbiTdHarSx3Ux4lMjfHbFJylYaw8TVhahn1sjuBUFamMi3+oon5 +QoYnGFWhgspam/gwmFQUpkeWJS/IJuRBlBpcAj/lluOFWzw+P7tHFnJV4iUisdl7 +5wMGKqP3HpBGwwAN1hmJ4w41J2IDcRWm79AnoKBZN2D4OJS44Hhw+LpMhoeU9uCu +AkXuZcK2o35pFnUHkpv1prxZg1g= +-----END CERTIFICATE----- diff --git a/efi/preinstall/testdata/PkKek-1-snakeoil.pem b/efi/preinstall/testdata/PkKek-1-snakeoil.pem new file mode 100644 index 00000000..73936f78 --- /dev/null +++ b/efi/preinstall/testdata/PkKek-1-snakeoil.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUSbJC1oRCJUbGkwfWHscBeZrRHZcwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UECgwJU25ha2UgT2lsMB4XDTE5MTEwMTIyMDI1NVoXDTE5MTIw +MTIyMDI1NVowFDESMBAGA1UECgwJU25ha2UgT2lsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzUDpJwDzDpLo2ytVRSgt/QWRYk/Yjae5fbujitq73XYL +uDZ+/Wf5U6zpOfyfzX/l5R0KCV9XYUJF47QEmNCnoWpg3cRdRry+3FIYtdnNK151 +AZ2L74OI4sMX1akSE+MfZFgdPFcm+n0uJgQuvRYGyYaR6N1wbhJ/2iOOba+sbKyc +aKiL1fSjip2criHA/05cYSomdUT+rTUZALFdCQuOU+gX8Rqhmfbo8VEE7MpE3nrv +HocQAFphyYgG8jadjggymE7sQEZGrBqOrwMDHitbpoGNlOI2VdFgL5jRKHuB61iC +kqTmSWuS4lbOEJmms6hhQnTnu/yK7O3NEWegAPMrtQIDAQABo1MwUTAdBgNVHQ4E +FgQUFD7OXb2T6sOysRo3hj2f15SX8I8wHwYDVR0jBBgwFoAUFD7OXb2T6sOysRo3 +hj2f15SX8I8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANZRB +NFVUVZVehpj3QGbbSjp77m0V6JrEYn6u/XjLRFsUNw5Hh35UCR0HkKZ0cLgrVKb/ +8yL6LaYLOY6yDwEFWMtLXiF2S4noO8raEgW6A7DHawb2Y4ZNFRO4oBkyWbtd36Uu +UfSszs2av048wb5J/pNedRSx8I/FiCNWummzpkBHzx023TdLPd8fmkmG7ZBpStN0 +Y//EE4DKTfHxAwt5w7WdZF5EY/KHPopnR+WSrdutRIK6zT+/+vKihtHYZbrv+7Ap +K7xOM/zJ6E9vUROmuOhL3YL3MuLn5qHEvhM0eMxEAlCnSJlFkQE4/RXhDpZJYbR7 +x+PQllgoo4H6W30Dew== +-----END CERTIFICATE----- diff --git a/efi/preinstall/testdata/shim-signed_1.54+15.7-0ubuntu1_amd64_latest.pk7 b/efi/preinstall/testdata/shim-signed_1.54+15.7-0ubuntu1_amd64_latest.pk7 new file mode 100644 index 00000000..72939f4a --- /dev/null +++ b/efi/preinstall/testdata/shim-signed_1.54+15.7-0ubuntu1_amd64_latest.pk7 @@ -0,0 +1,202 @@ +-----BEGIN PKCS7----- +MIIlVgYJKoZIhvcNAQcCoIIlRzCCJUMCAQExDzANBglghkgBZQMEAgEFADBcBgor +BgEEAYI3AgEEoE4wTDAXBgorBgEEAYI3AgEPMAkDAQCgBKICgAAwMTANBglghkgB +ZQMEAgEFAAQgv2tt/bH2Q1qB5ICNt/hG2G0XBWbkdT1DhP2rZQS+T7mgggssMIIF +FDCCA/ygAwIBAgITMwAAAE9TYSWm1mSIZwABAAAATzANBgkqhkiG9w0BAQsFADCB +gTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl +ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjErMCkGA1UEAxMi +TWljcm9zb2Z0IENvcnBvcmF0aW9uIFVFRkkgQ0EgMjAxMTAeFw0yMjA1MDUxOTI0 +MDdaFw0yMzA1MDQxOTI0MDdaMIGGMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +cnBvcmF0aW9uMTAwLgYDVQQDEydNaWNyb3NvZnQgV2luZG93cyBVRUZJIERyaXZl +ciBQdWJsaXNoZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9N/de +hneFoFQ2OS0FJ6hGrrWaTEeEAlcCMftdUTzbcqCOyjWtf7LzbwJ+NH1s02Z0JPH7 +h5gbGTWhtv4+sUBoWEOBpERCqYV/QLYJuCv23wCJ10+YV/aub++TBlASI5mC54Qs +n2vOojzJHn9aBU8/JBHQSB199OdI/40dcVy8d4d6AvUpdux0nrl5XF9RCRcYgopp +N73u8Kpspy+qCYfD+jDdoo+HVNsnckRmx2OZcv5Tk9edlOskjLc+y6rbJrb2qpnu +ebmwQVFmjB0PymRwD2AQ1Xmhh+uRCXU2YjXyzoCecu6OjA0F+f3cMHLr4uPSXNLb +S9NIukhSskMhXNaXAgMBAAGjggF8MIIBeDAfBgNVHSUEGDAWBgorBgEEAYI3UAIB +BggrBgEFBQcDAzAdBgNVHQ4EFgQU3TxskDSx/rhVfGF8LoWhAE2dhOMwUAYDVR0R +BEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBS +aWNvMRYwFAYDVQQFEw0yMjk5MTErNDcwMDI3MB8GA1UdIwQYMBaAFBOtv0MJvYJw +nIzVTzFu1SKYihvUMFMGA1UdHwRMMEowSKBGoESGQmh0dHA6Ly93d3cubWljcm9z +b2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvclVFRkNBMjAxMV8yMDExLTA2LTI3LmNy +bDBgBggrBgEFBQcBAQRUMFIwUAYIKwYBBQUHMAKGRGh0dHA6Ly93d3cubWljcm9z +b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljQ29yVUVGQ0EyMDExXzIwMTEtMDYtMjcu +Y3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAIzTLlA0cQVRe8UM +h4ggXoLd2xazklWP2Yv5BJA+GFUPER9Ym6pBN0l2bJ/nR4tXVyf/h0mvSH64wZ1k +H6NH4sKDQFCNNMl83vsjijJIkvF7SpA+4d3HHIm//uNG/QowMbkpeM/n9LRMatG3 +AUiJcl7Cl7igStx5jt1ffFG1THg6WNWwLyQUuNnYhnMV/6Ai3/e3wRnTulzNNN4P +LE8/uTbmh2RAvnnaqpoDMT9dr/vda+NuqHSu8/Z99vpQ6rf5LAPd4mLysGj8EJ+6 +gS4zasQQs6/dHEzdlRaOdu3i6+IjImtdWd+2ILvcJuRxykILW9LflHqwqP52X482 +pwSTdyUwggYQMIID+KADAgECAgphCNPEAAAAAAAEMA0GCSqGSIb3DQEBCwUAMIGR +MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk +bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJN +aWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9v +dDAeFw0xMTA2MjcyMTIyNDVaFw0yNjA2MjcyMTMyNDVaMIGBMQswCQYDVQQGEwJV +UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29y +cG9yYXRpb24gVUVGSSBDQSAyMDExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEApQhsTMdFCWpLDKTAh38GdQxDAVRk4BZ/B+2SfQuyc78MCsZKRWGgxRYt +ltP1K6D7TUmbQYCQPLlU/ea80Z3EpBiKf0GKXFmDaDK7jEfJ7nG8IU+ainz/RD+N +jzKyJkiudbXuyUweShl+5IKaHXh3TQywvfYP0xbTvPorpVE4XfX7utt4Atv/7Aob +ltWDuBkT6bbAe0B74R8oJ8n671ZeHOZ+lH7A8ESyeTnl2rJii02/OHDiaCQUyTOk +CDfVWGle03ztwQRTCOdOsCqHYwhhb2MVWeqyK3nXDGFnilv9Xq2Hf7qGZ09xWBIi +BCIizovvVHEAzlA1WHaVCO5qsaIB1QIDAQABo4IBdjCCAXIwEgYJKwYBBAGCNxUB +BAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU+MFrt393U0rzJTcdTqEmew8gcIAwHQYD +VR0OBBYEFBOtv0MJvYJwnIzVTzFu1SKYihvUMBkGCSsGAQQBgjcUAgQMHgoAUwB1 +AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA +FEVmUkPhflgRv9ZOniNVCDs6ImqoMFwGA1UdHwRVMFMwUaBPoE2GS2h0dHA6Ly9j +cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY0NvclRoaVBhck1h +clJvb18yMDEwLTEwLTA1LmNybDBgBggrBgEFBQcBAQRUMFIwUAYIKwYBBQUHMAKG +RGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljQ29yVGhpUGFy +TWFyUm9vXzIwMTAtMTAtMDUuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQA1CEL/MMzO +93YMrRBoWDUpRjJ2J3zvEkEnQhtKqm2BOEhZE1Xz6Vg0phYLgqpdrYLagINBBo+0 +HfIDufMaXRvxUJD5s1WEQigcIL2yrlEUxcCsl5UhHJDbD/x3npVzkYjKvb1SuQVQ +Dd9XnqBh7Q3lbSXZQA8XQMjOo0rCTa+aEh0IVI+9x7y5Kz1JKx8y/GohaU+byH5C +NPw2BheLjyBAwLOaJXUnzckDo/Zd0ec2VHq5ULXTEtEHv7t039wej4DV7Rj0LxQW +ay/eZoywI+XHhNjt6sEzgq1WSxgt8WiVB83P8HLwrrvdhoWYLCFMMyvwD0rwaIe1 +klUydaFqgmo8oyURpO2t1wSuy9hAWaCE0ZVMYpEiGnQdjD1HDkSm5LCbNDWx+rZT +qCyB7KQFcciduLroG0Rm5EdUDo5Wf7OfFpiyhtBoPpAjtS9ej1CFjcaNgl9BofQu +DeCZ0mx15LZptSGG+gfR9uJN0dqtLHdTHiUyN8dsUnKVhrDxNWFqGfWyO4FQVqYy +Lf6iiflChicYVaGCylqb+DCYVBSmR5YlL8gm5EGUGlwCP+WW44VbPD4/u0cWclXi +JSKx2XvnAwYqo/cekEbDAA3WGYnjDjUnYgNxFabv0CegoFk3YPg4lLjgeHD4ukyG +h5T24K4CRe5lwrajfmkWdQeSm/WmvFmDWDGCGZ0wghmZAgEBMIGZMIGBMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe +MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3Nv +ZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDExAhMzAAAAT1NhJabWZIhnAAEAAABP +MA0GCWCGSAFlAwQCAQUAoIHGMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG +CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCDZBA1F +Ur81fp3a1hnQNy8/HMZ2KhUbLWprMYyHJfrtxzBaBgorBgEEAYI3AgEMMUwwSqAc +gBoAQwBhAG4AbwBuAGkAYwBhAGwAIABMAHQAZKEqgChodHRwczovL3d3dy5taWNy +b3NvZnQuY29tL2VuLXVzL3dpbmRvd3MgMA0GCSqGSIb3DQEBAQUABIIBACq4xJz0 +8TtA62j1LrL2uKd/59nHbqSt9Ho+z2ZMGDzovYf7YoqQIgWhG+wDRazOgNsgG6lU +KNmUIYRnBo/b07QC+T47Rph0CZyMVa7957wyR+qWp1T6Ul9I68jfARRF6FdC93xq +oqoBedARCvBER9P1N3+Afgdww9eTxzRi0vX/uipYhbz0NGfDMI6dWkte0/kjQVs1 +NDL4xmqCJ85fG+o89OCGhTFXpfoa88T+qxgi0XF+0AkvSBCwRzNJsl6h9Krri1z8 +Z2EFdtRULz4PE/W6jZ4fJkJJ7reHDmvBMorEEScqSmnLs6cr45JYPGMaQFUH10j8 +JbraVUXnH4RkGTuhghcLMIIXBwYKKwYBBAGCNwMDATGCFvcwghbzBgkqhkiG9w0B +BwKgghbkMIIW4AIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBVQYLKoZIhvcNAQkQAQSg +ggFEBIIBQDCCATwCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgLYbs +Fpr8nKBWQEp9ZE2xbiEtTXa/YRp1qrQueCJojvECBmPHFcVRahgTMjAyMzAxMTgw +MDM3NTEuNzg2WjAEgAIB9KCB1KSB0TCBzjELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m +dCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVl +cnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYwQkMtRTM4My0yNjM1 +MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIRXjCCBxAw +ggT4oAMCAQICEzMAAAGmWUWDOU2e60sAAQAAAaYwDQYJKoZIhvcNAQELBQAwfDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v +bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj +cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIwMzAyMTg1MTIxWhcNMjMw +NTExMTg1MTIxWjCBzjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +bjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAk +BgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYwQkMtRTM4My0yNjM1MSUwIwYDVQQDExxN +aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEA2Zi/e1Ij58n81AmePPsm8Kdz5ebSsqh71goPgy8xgK6Xt6B2 +tP/O/m8VtCCM1DvjrvZ83B5rO2RHrlXzLb27k8vax/TWn65yF7Rm7i1KKD4axDpl +CX22M9EBj/chMEcN4hjK+rxad737s2g8uHENI7p21ftgK5DjNxM/dIToy8Hhvk2K +CF22+hlVpiTWVemNRN92YqhfUAGrWwltQtKdKLRB3i++XeZn2PHC/11H+eVk/raW +tlhmrss+0cPoGWZyUHk9Pz0OdKbWyNpmcUesrM6yarkaWYvlIW6AIJk6grPXfcUl +5BoUxxcFlIJCM0AFYFschEITXKwccbzcN2idGacLwQ6Vh5HBNbP9ALPqrSuI4htj +IL8DYGBQSm73/0TKatOzIyvb/NLwZ0TJtDlbt/RatyuYoH9jrb6DpOZ85Lw21T4v +WMago0bpDlGV8nBm7wn9D12Xg7HIcq7Lvz7CboewXu4CLOmxaHrdRRqgr84ZCIEb +c0n6R5/l5ame9rhkl+ECephMBkPW4eB/xV9COeXQEHZhfMr1ZpOp17x37yoLFUqv +mEli9s75ff7aTk8KKtQr9Juit5f7FSFVpASFUNiqVq3I+20jtnYiuSEzPAW9z6nR +B7IyI2ajZwFl6PHyJwM5xSJ3DKYNRioY8TswDy+0pbd955JJgmwISS5Q7+8CAwEA +AaOCATYwggEyMB0GA1UdDgQWBBQ6VCE7/MaWor31SQ0v8a78CvI32DAfBgNVHSME +GDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRw +Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1l +LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsG +AQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p +Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB +Af8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4ICAQCA +wPFYNOkaoucWg+Gb+IN/AcYXzGvY1usmXx6ASDZOFMmxN/TAET5lCydh+tGZcFt7 +qwJctU3vSo+4j44Rs3kw5qLsG57X/iPlVORaq4fkZl5Vq3Y350PuVJRanR1TyP64 +GEEvkYVKagNVWb7NbYZHaO48jW/bngAlNvaXjnxqeWQmMa+ZifYG1FLXeH/ANHuG +tBojsGB3IdYBXn4cSPlSGsiuu+3AmKK9JpQQDeorpkr+tkhC/+45EOQ43D7akccg +TVJeb9YiWGtVLYciiB+vcmOq9mKifoslIPvjWPzFUMuIKXABuykehUWPG3EFwyOo +/HppYIlLy+NKhOeGRXg87nmaqwztDxdBEZCEDvDjM1A4m72QPjEV1ik9SYs391oh +wQSWh8GMbP6wR3UHjKqoiTe7YbhXKBNcWa2EvxyFKjuv4Yi9OpYqFID+xqdLg3eM +KAIJ7cVNImyniDmfBq8u9YC3Nw4i9JGisaYB43SbbCDMEr3lP+qCsYYNdKizUk0N +ZFUGc/SqzDVCirkbQPyHG9A+zdfjcoG/UYmXTCjmtwL704xbEmUHreC1OhCwDUIS +tihgsxm1TMkvviPBmT+CukcRCEiEHeyd4LzDMYom5+3tg78dYKm7B0KEiPKdOcGH +7IUYx2DfBGshs5zD+IqZdmikxNAw5yYh4jAkB7MDsDCCB3EwggVZoAMCAQICEzMA +AAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290 +IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMw +MDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3u +nAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1 +jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZT +fDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+ +jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c ++gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+ +cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C6 +26p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV +2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoS +CtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxS +UV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJp +xq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkr +BgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0A +XmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYI +KwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9S +ZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIE +DB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVo +dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29D +ZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAC +hj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1 +dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwEx +JFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts +0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9I +dQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYS +EhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMu +LGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT9 +9kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2z +AVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6Ile +T53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6l +MVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbh +IurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3u +gm2lBRDBcQZqELQdVTNYs6FwZvKhggLRMIICOgIBATCB/KGB1KSB0TCBzjELMAkG +A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9z +b2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1Mg +RVNOOjYwQkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt +cCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBqdDOtlb1MH3dV7s9rhQ9qjZ98raCB +gzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD +VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk +BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEB +BQUAAgUA53GURTAiGA8yMDIzMDExODAxNDAyMVoYDzIwMjMwMTE5MDE0MDIxWjB2 +MDwGCisGAQQBhFkKBAExLjAsMAoCBQDncZRFAgEAMAkCAQACAQACAf8wBwIBAAIC +ENwwCgIFAOdy5cUCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK +MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAgWBEXeOLc +byEsJGZGhxan3vtyxbL4s28qYD5g+z1VlSF35DJSf8Vi1HxsCQNKGCh38A6AkdRC +eXo4Ofj8v2o3wN6HPi0lIVkCRqnRgd45KJ5NsnsAxF1z1cWIwBZcqfZQTVk+0dCo +Pzf5Ayk2vWIaSbtE4RkpiWx7EBwPT1O6XDGCBA0wggQJAgEBMIGTMHwxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w +HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m +dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABpllFgzlNnutLAAEAAAGmMA0GCWCG +SAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZI +hvcNAQkEMSIEIKLjEawXvnbtn1DaW5VIbwoiHUP6odIKeoNZsz/mSmAjMIH6Bgsq +hkiG9w0BCRACLzGB6jCB5zCB5DCBvQQggwsZi8M/dH1r4TCmyUwEGirdw6F3ogIX +6fEw/bYEqw0wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv +cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT +MwAAAaZZRYM5TZ7rSwABAAABpjAiBCDb9FmQBGXmGh0pzP9LsVS9/SeMEBAJgdhz +rEL+VofYIzANBgkqhkiG9w0BAQsFAASCAgCgixZlYX+kfi0c7Ko87ZMGm1bgMXSU +RD/Q+SF6k88Z/H5pQ97oGzPYv/w8Jw6cF6Z5IzDFl6N4RE5vwnU46J3e7HdT9bK/ +QacgVlyvJ7WGvh69xKa4G0MkYGLCcLCDg9a8YuOYT5H1G/bmPRMebxT1eCPwcbKM +pOQRlUDztIEnvwGqmkjIbC/AmSTv+DMcF84xvE3or3uOTJqociOtBU9TrMZ5SM8j +J8XvnrNZTMAXi+7DyVrkyot9gNwK++S1GuKCGLL0trjOTf0dcs6D/XRM2u4OdvQo +hQq/+TGexY5+DduMWP0iTm4d3FGsJYkF4qhXcwvshoqrxl0YTqtneT5rASSunM5W +u1epuX05ApcGJIkxAV45+EJtLgGjXW7gSa++Io4ofN6v8wWUeSKgkIisY5+cYARi +rPTu2OygfrO7ftCb7N16BStfVsWdMCaFe0XbJqFtpfAFudorcndPeVPLDvlaUhLC +drTQN1euP8ZVLBQXqcoa9U1sspd+a48DBzqKs1XC5do6nHCSOoGkoETNtVcGbfTJ +TmYsRXI3NJek8DX9fZdyrHezHhnuoTFxXWHOg2Y1j+o10pPvDKdMLFM5Dzwg5ODG +22QCC1/pNbWxhna1AW+xOuvUEEsrhsWJf8TpDtnTZj1knu18HV8khtbg+V+tEtuX +2rPJ2lFgo7dBLg== +-----END PKCS7----- diff --git a/internal/efi/pe.go b/internal/efi/pe.go new file mode 100644 index 00000000..e9e22d9d --- /dev/null +++ b/internal/efi/pe.go @@ -0,0 +1,95 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2023 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package efi + +import ( + "crypto" + "errors" + "fmt" + "io" + + efi "github.com/canonical/go-efilib" + pe "github.com/snapcore/secboot/internal/pe1.14" +) + +const ( + certTableIndex = 4 // Index of the Certificate Table entry in the Data Directory of a PE image optional header +) + +func SecureBootSignaturesFromPEFile(pefile *pe.File, r io.ReaderAt) ([]*efi.WinCertificateAuthenticode, error) { + // Obtain security directory entry from optional header + var dd []pe.DataDirectory + switch oh := pefile.OptionalHeader.(type) { + case *pe.OptionalHeader32: + dd = oh.DataDirectory[0:oh.NumberOfRvaAndSizes] + case *pe.OptionalHeader64: + dd = oh.DataDirectory[0:oh.NumberOfRvaAndSizes] + default: + return nil, errors.New("cannot obtain security directory entry: no optional header") + } + + if len(dd) <= certTableIndex { + // This image doesn't include a certificate table entry, so has no signatures. + return nil, nil + } + + // Create a reader for the security directory entry, which points to one or more WIN_CERTIFICATE structs + certReader := io.NewSectionReader( + r, + int64(dd[certTableIndex].VirtualAddress), + int64(dd[certTableIndex].Size)) + + // Binaries can have multiple signers - this is achieved using multiple single-signed Authenticode + // signatures - see section 32.5.3.3 ("Secure Boot and Driver Signing - UEFI Image Validation - + // Signature Database Update - Authorization Process") of the UEFI Specification, version 2.8. + var sigs []*efi.WinCertificateAuthenticode + +SignatureLoop: + for i := 0; ; i++ { + // Signatures in this section are 8-byte aligned - see the PE spec: + // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only + off, _ := certReader.Seek(0, io.SeekCurrent) + alignSize := (8 - (off & 7)) % 8 + certReader.Seek(alignSize, io.SeekCurrent) + + c, err := efi.ReadWinCertificate(certReader) + switch { + case errors.Is(err, io.EOF): + break SignatureLoop + case err != nil: + return nil, fmt.Errorf("cannot decode WIN_CERTIFICATE from security directory entry %d: %w", i, err) + } + + sig, ok := c.(*efi.WinCertificateAuthenticode) + if !ok { + return nil, fmt.Errorf("unexpected WIN_CERTIFICATE type from security directory entry %d: not an Authenticode signature", i) + } + + // Reject any signature with a digest algorithm other than SHA256, as that's the only algorithm used + // for binaries we're expected to support, and therefore required by the UEFI implementation. + if sig.DigestAlgorithm() != crypto.SHA256 { + return nil, fmt.Errorf("signature from security directory entry %d has unexpected digest algorithm", i) + } + + sigs = append(sigs, sig) + } + + return sigs, nil +} diff --git a/internal/efitest/db.go b/internal/efitest/db.go index bd57b8b0..806af3de 100644 --- a/internal/efitest/db.go +++ b/internal/efitest/db.go @@ -66,3 +66,25 @@ func NewSignatureListNullSHA256(owner efi.GUID) *efi.SignatureList { }, } } + +func NewSignatureListDigests(c *C, alg crypto.Hash, owner efi.GUID, digests ...[]byte) *efi.SignatureList { + var eslType efi.GUID + switch alg { + case crypto.SHA1: + eslType = efi.CertSHA1Guid + case crypto.SHA256: + eslType = efi.CertSHA256Guid + case crypto.SHA384: + eslType = efi.CertSHA384Guid + case crypto.SHA512: + eslType = efi.CertSHA512Guid + default: + c.Fatal("unsupported digest") + } + + esl := &efi.SignatureList{Type: eslType} + for _, digest := range digests { + esl.Signatures = append(esl.Signatures, &efi.SignatureData{Owner: owner, Data: digest}) + } + return esl +} diff --git a/internal/efitest/log.go b/internal/efitest/log.go index e3ddac41..4b3e92df 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -21,6 +21,7 @@ package efitest import ( "bytes" + "crypto" _ "embed" "encoding/binary" "io" @@ -110,6 +111,7 @@ type LogOptions struct { NoCallingEFIApplicationEvent bool // omit the EV_EFI_ACTION "Calling EFI Application from Boot Option" event. IncludeOSPresentFirmwareAppLaunch efi.GUID // include a flash based application launch in the log as part of the OS-present phase NoSBAT bool // omit the SbatLevel measurement to mimic older versions of shim + PreOSVerificationUsesDigests crypto.Hash // Whether Driver or SysPrep launches are verified using a digest } // NewLog creates a mock TCG log for testing. The log will look like a standard @@ -239,6 +241,25 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { }) } + db := efi.SignatureDatabase{ + NewSignatureListX509(c, testutil.DecodePEMType(c, "CERTIFICATE", msPCACert), efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b})), + NewSignatureListX509(c, testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert), efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b})), + } + if opts.PreOSVerificationUsesDigests != crypto.Hash(0) { + alg := opts.PreOSVerificationUsesDigests + var digests [][]byte + + h := alg.New() + io.WriteString(h, "mock EFI driver") + digests = append(digests, h.Sum(nil)) + + h = alg.New() + io.WriteString(h, "mock sysprep app") + digests = append(digests, h.Sum(nil)) + + db = append(db, NewSignatureListDigests(c, alg, efi.MakeGUID(0x70564dce, 0x9afc, 0x4ee3, 0x85fc, [...]uint8{0x94, 0x96, 0x49, 0xd7, 0xe4, 0x5c}), digests...)) + } + for _, sbvar := range []struct { name efi.VariableDescriptor data []byte @@ -257,10 +278,7 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { }, { name: efi.VariableDescriptor{Name: "db", GUID: efi.ImageSecurityDatabaseGuid}, - data: MakeVarPayload(c, efi.SignatureDatabase{ - NewSignatureListX509(c, testutil.DecodePEMType(c, "CERTIFICATE", msPCACert), efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b})), - NewSignatureListX509(c, testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert), efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b})), - }), + data: MakeVarPayload(c, db), }, { name: efi.VariableDescriptor{Name: "dbx", GUID: efi.ImageSecurityDatabaseGuid}, @@ -305,11 +323,21 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { // Mock EFI driver launch if opts.IncludeDriverLaunch { - c.Assert(opts.DisallowPreOSVerification, testutil.IsFalse) + pe := bytesHashData("mock EFI driver") if !opts.SecureBootDisabled { - esd := &efi.SignatureData{ - Owner: efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b}), - Data: testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert)} + var esd *efi.SignatureData + if opts.PreOSVerificationUsesDigests == crypto.Hash(0) { + esd = &efi.SignatureData{ + Owner: efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b}), + Data: testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert)} + } else { + h := opts.PreOSVerificationUsesDigests.New() + pe.Write(h) + esd = &efi.SignatureData{ + Owner: efi.MakeGUID(0x70564dce, 0x9afc, 0x4ee3, 0x85fc, [...]uint8{0x94, 0x96, 0x49, 0xd7, 0xe4, 0x5c}), + Data: h.Sum(nil), + } + } esdBytes := new(bytes.Buffer) esd.Write(esdBytes) data := &tcglog.EFIVariableData{ @@ -321,7 +349,6 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { eventType: tcglog.EventTypeEFIVariableAuthority, data: data}) } - pe := bytesHashData("mock EFI driver") data := &tcglog.EFIImageLoadEvent{ LocationInMemory: 0x41a2f024, LengthInMemory: 659024, @@ -350,23 +377,38 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { // Mock sysprep app launch if opts.IncludeSysPrepAppLaunch { - c.Assert(opts.DisallowPreOSVerification, testutil.IsFalse) - if !opts.SecureBootDisabled && !opts.IncludeDriverLaunch { - esd := &efi.SignatureData{ - Owner: efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b}), - Data: testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert)} - esdBytes := new(bytes.Buffer) - esd.Write(esdBytes) - data := &tcglog.EFIVariableData{ - VariableName: efi.ImageSecurityDatabaseGuid, - UnicodeName: "db", - VariableData: esdBytes.Bytes()} - builder.hashLogExtendEvent(c, data, &logEvent{ - pcrIndex: 7, - eventType: tcglog.EventTypeEFIVariableAuthority, - data: data}) - } pe := bytesHashData("mock sysprep app") + if !opts.SecureBootDisabled { + var esd *efi.SignatureData + if opts.PreOSVerificationUsesDigests == crypto.Hash(0) { + if !opts.IncludeDriverLaunch { + // This has already been measured + esd = &efi.SignatureData{ + Owner: efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b}), + Data: testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert)} + } + } else { + h := opts.PreOSVerificationUsesDigests.New() + pe.Write(h) + esd = &efi.SignatureData{ + Owner: efi.MakeGUID(0x70564dce, 0x9afc, 0x4ee3, 0x85fc, [...]uint8{0x94, 0x96, 0x49, 0xd7, 0xe4, 0x5c}), + Data: h.Sum(nil), + } + } + if esd != nil { + esdBytes := new(bytes.Buffer) + esd.Write(esdBytes) + data := &tcglog.EFIVariableData{ + VariableName: efi.ImageSecurityDatabaseGuid, + UnicodeName: "db", + VariableData: esdBytes.Bytes()} + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: 7, + eventType: tcglog.EventTypeEFIVariableAuthority, + data: data}) + } + } + data := &tcglog.EFIImageLoadEvent{ LocationInMemory: 0x18e4b324, LengthInMemory: 120948, @@ -528,7 +570,7 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { } // Mock shim launch - if !opts.SecureBootDisabled && !opts.IncludeDriverLaunch && !opts.IncludeSysPrepAppLaunch { + if !opts.SecureBootDisabled && (opts.PreOSVerificationUsesDigests != crypto.Hash(0) || (!opts.IncludeDriverLaunch && !opts.IncludeSysPrepAppLaunch)) { esd := &efi.SignatureData{ Owner: efi.MakeGUID(0x77fa9abd, 0x0359, 0x4d32, 0xbd60, [...]uint8{0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b}), Data: testutil.DecodePEMType(c, "CERTIFICATE", msUefiCACert)} From 90036c5f694860b74ed686249f69e1fdb3ea988d Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Sun, 13 Oct 2024 23:13:16 +0100 Subject: [PATCH 2/6] internal/efi: Add tests for SecureBootSignatures helper --- internal/efi/pe_test.go | 75 +++++++++++++++++++ internal/efi/testdata/amd64/mockkernel1.efi | 1 + .../testdata/amd64/mockshim.efi.signed.1.1.1 | 1 + .../amd64/mockshim.efi.signed.1.2.1+1.1.1 | 1 + 4 files changed, 78 insertions(+) create mode 100644 internal/efi/pe_test.go create mode 120000 internal/efi/testdata/amd64/mockkernel1.efi create mode 120000 internal/efi/testdata/amd64/mockshim.efi.signed.1.1.1 create mode 120000 internal/efi/testdata/amd64/mockshim.efi.signed.1.2.1+1.1.1 diff --git a/internal/efi/pe_test.go b/internal/efi/pe_test.go new file mode 100644 index 00000000..51ec0187 --- /dev/null +++ b/internal/efi/pe_test.go @@ -0,0 +1,75 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2023 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package efi_test + +import ( + "crypto" + "os" + + . "github.com/snapcore/secboot/internal/efi" + pe "github.com/snapcore/secboot/internal/pe1.14" + "github.com/snapcore/secboot/internal/testutil" + + . "gopkg.in/check.v1" +) + +type secureBootSignaturesSuite struct{} + +var _ = Suite(&secureBootSignaturesSuite{}) + +func (s *secureBootSignaturesSuite) testSecureBootSignatures(c *C, path string, digests [][]byte) { + //path, err := filepath.EvalSymlinks(path) + //c.Assert(err, IsNil) + + f, err := os.Open(path) + c.Assert(err, IsNil) + defer f.Close() + + pefile, err := pe.NewFile(f) + c.Assert(err, IsNil) + + sigs, err := SecureBootSignaturesFromPEFile(pefile, f) + c.Check(err, IsNil) + c.Assert(sigs, HasLen, len(digests)) + + for i, expected := range digests { + h := crypto.SHA256.New() + h.Write(sigs[i].GetSigner().RawTBSCertificate) + c.Check(h.Sum(nil), DeepEquals, expected) + } +} + +func (s *secureBootSignaturesSuite) TestSecureBootSignatures(c *C) { + s.testSecureBootSignatures(c, + "testdata/amd64/mockshim.efi.signed.1.1.1", + [][]byte{testutil.DecodeHexString(c, "4c503fa92a4d6ab180962c29aa8324cc873e8f74b259fb28347443ac8fef6af8")}) +} + +func (s *secureBootSignaturesSuite) TestSecureBootSignaturesUnsigned(c *C) { + s.testSecureBootSignatures(c, "testdata/amd64/mockkernel1.efi", nil) +} + +func (s *secureBootSignaturesSuite) TestSecureBootSignaturesDualSigned(c *C) { + s.testSecureBootSignatures(c, + "testdata/amd64/mockshim.efi.signed.1.2.1+1.1.1", + [][]byte{ + testutil.DecodeHexString(c, "713af30678aba44b6c437cfc4fec26d386d3e2fea75b055df010d4af7b11b484"), + testutil.DecodeHexString(c, "4c503fa92a4d6ab180962c29aa8324cc873e8f74b259fb28347443ac8fef6af8")}) +} diff --git a/internal/efi/testdata/amd64/mockkernel1.efi b/internal/efi/testdata/amd64/mockkernel1.efi new file mode 120000 index 00000000..5e7c0295 --- /dev/null +++ b/internal/efi/testdata/amd64/mockkernel1.efi @@ -0,0 +1 @@ +../../../../efi/testdata/amd64/mockkernel1.efi \ No newline at end of file diff --git a/internal/efi/testdata/amd64/mockshim.efi.signed.1.1.1 b/internal/efi/testdata/amd64/mockshim.efi.signed.1.1.1 new file mode 120000 index 00000000..21b73ea6 --- /dev/null +++ b/internal/efi/testdata/amd64/mockshim.efi.signed.1.1.1 @@ -0,0 +1 @@ +../../../../efi/testdata/amd64/mockshim.efi.signed.1.1.1 \ No newline at end of file diff --git a/internal/efi/testdata/amd64/mockshim.efi.signed.1.2.1+1.1.1 b/internal/efi/testdata/amd64/mockshim.efi.signed.1.2.1+1.1.1 new file mode 120000 index 00000000..92cb86a3 --- /dev/null +++ b/internal/efi/testdata/amd64/mockshim.efi.signed.1.2.1+1.1.1 @@ -0,0 +1 @@ +../../../../efi/testdata/amd64/mockshim.efi.signed.1.2.1+1.1.1 \ No newline at end of file From 5e42dd09788254b38e08faa31dcb33b7e04202b3 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Sun, 13 Oct 2024 23:46:10 +0100 Subject: [PATCH 3/6] preinstall: Reduce some code duplication This moves the code to read the current boot load option from the log into a helper function, as the same code was used both in the PCR4 and PCR7 checks. --- efi/preinstall/check_pcr4.go | 67 +--------- efi/preinstall/check_pcr4_test.go | 64 ---------- efi/preinstall/check_pcr7.go | 15 +-- efi/preinstall/export_test.go | 1 + efi/preinstall/load_option_util.go | 100 +++++++++++++++ efi/preinstall/load_option_util_test.go | 157 ++++++++++++++++++++++++ 6 files changed, 266 insertions(+), 138 deletions(-) create mode 100644 efi/preinstall/load_option_util.go create mode 100644 efi/preinstall/load_option_util_test.go diff --git a/efi/preinstall/check_pcr4.go b/efi/preinstall/check_pcr4.go index 7cdade63..7054038f 100644 --- a/efi/preinstall/check_pcr4.go +++ b/efi/preinstall/check_pcr4.go @@ -25,7 +25,6 @@ import ( "errors" "fmt" "io" - "strings" efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" @@ -38,58 +37,6 @@ var ( efiComputePeImageDigest = efi.ComputePeImageDigest ) -// readLoadOptionFromLog reads the corresponding Boot#### load option from the log, -// which reflects the value of it at boot time, as opposed to reading it from an -// EFI variable which may have been modified since booting. -func readLoadOptionFromLog(log *tcglog.Log, n uint16) (*efi.LoadOption, error) { - events := log.Events - for len(events) > 0 { - ev := events[0] - events = events[1:] - - if ev.PCRIndex != internal_efi.PlatformConfigPCR { - continue - } - - if ev.EventType != tcglog.EventTypeEFIVariableBoot && ev.EventType != tcglog.EventTypeEFIVariableBoot2 { - // not a boot variable - continue - } - - data, ok := ev.Data.(*tcglog.EFIVariableData) - if !ok { - // decode error data is guaranteed to implement the error interface - return nil, fmt.Errorf("boot variable measurement has wrong data format: %w", ev.Data.(error)) - } - if data.VariableName != efi.GlobalVariable { - // not a global variable - continue - } - if !strings.HasPrefix(data.UnicodeName, "Boot") || len(data.UnicodeName) != 8 { - // name has unexpected prefix or length - continue - } - - var x uint16 - if c, err := fmt.Sscanf(data.UnicodeName, "Boot%x", &x); err != nil || c != 1 { - continue - } - if x != n { - // wrong load option - continue - } - - // We've found the correct load option. Decode it from the data stored in the log. - opt, err := efi.ReadLoadOption(bytes.NewReader(data.VariableData)) - if err != nil { - return nil, fmt.Errorf("cannot read load option from event data: %w", err) - } - return opt, nil - } - - return nil, errors.New("cannot find specified boot option") -} - // isLaunchedFromLoadOption returns true if the supplied EV_EFI_BOOT_SERVICES_APPLICATION event // is associated with the supplied load option. This will panic if the event is of the // wrong type or the event data decodes incorrectly. This works by doing a device path match, @@ -214,17 +161,11 @@ func checkBootManagerCodeMeasurements(ctx context.Context, env internal_efi.Host return 0, fmt.Errorf("cannot obtain boot option support: %w", err) } - // Obtain the BootCurrent variable and use this to obtain the corresponding load entry - // that was measured to the log. BootXXXX variables are measured to the TPM and so we don't - // need to read back from an EFI variable that could have been modified between boot time - // and now. - current, err := efi.ReadBootCurrentVariable(varCtx) - if err != nil { - return 0, fmt.Errorf("cannot read BootCurrent variable: %w", err) - } - bootOpt, err := readLoadOptionFromLog(log, current) + // Obtain the load option from the current boot so we can identify which load + // event corresponds to the initial OS boot loader. + bootOpt, err := readCurrentBootLoadOptionFromLog(varCtx, log) if err != nil { - return 0, fmt.Errorf("cannot read current Boot%04x load option from log: %w", current, err) + return 0, err } var ( diff --git a/efi/preinstall/check_pcr4_test.go b/efi/preinstall/check_pcr4_test.go index 05d7aa48..a28ca020 100644 --- a/efi/preinstall/check_pcr4_test.go +++ b/efi/preinstall/check_pcr4_test.go @@ -22,7 +22,6 @@ package preinstall_test import ( "context" "crypto" - "errors" "io" efi "github.com/canonical/go-efilib" @@ -40,69 +39,6 @@ type pcr4Suite struct{} var _ = Suite(&pcr4Suite{}) -func (s *pcr4Suite) TestReadLoadOptionFromLog(c *C) { - log := efitest.NewLog(c, &efitest.LogOptions{}) - opt, err := ReadLoadOptionFromLog(log, 3) - c.Assert(err, IsNil) - c.Check(opt, DeepEquals, &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - OptionalData: []byte{}, - }) -} - -func (s *pcr4Suite) TestReadLoadOptionFromLogNotExist(c *C) { - log := efitest.NewLog(c, &efitest.LogOptions{}) - _, err := ReadLoadOptionFromLog(log, 10) - c.Check(err, ErrorMatches, `cannot find specified boot option`) -} - -func (s *pcr4Suite) TestReadLoadOptionFromLogInvalidData(c *C) { - log := efitest.NewLog(c, &efitest.LogOptions{}) - for _, ev := range log.Events { - if ev.PCRIndex != internal_efi.PlatformConfigPCR { - continue - } - if ev.EventType != tcglog.EventTypeEFIVariableBoot { - continue - } - data, ok := ev.Data.(*tcglog.EFIVariableData) - c.Assert(ok, testutil.IsTrue) - if data.UnicodeName != "Boot0003" { - continue - } - ev.Data = &invalidEventData{errors.New("some error")} - } - _, err := ReadLoadOptionFromLog(log, 3) - c.Check(err, ErrorMatches, `boot variable measurement has wrong data format: some error`) -} - -func (s *pcr4Suite) TestReadLoadOptionFromLogInvalidVariableName(c *C) { - log := efitest.NewLog(c, &efitest.LogOptions{}) - for _, ev := range log.Events { - if ev.PCRIndex != internal_efi.PlatformConfigPCR { - continue - } - if ev.EventType != tcglog.EventTypeEFIVariableBoot { - continue - } - data, ok := ev.Data.(*tcglog.EFIVariableData) - c.Assert(ok, testutil.IsTrue) - data.VariableName = efi.MakeGUID(0x6be4d043, 0x2ded, 0x4669, 0xa43b, [...]byte{0x91, 0x37, 0xb8, 0xa9, 0xd1, 0xa4}) - } - _, err := ReadLoadOptionFromLog(log, 3) - c.Check(err, ErrorMatches, `cannot find specified boot option`) -} - func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGood(c *C) { opt := &efi.LoadOption{ Attributes: 1, diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go index b4e35f0e..adfabc6f 100644 --- a/efi/preinstall/check_pcr7.go +++ b/efi/preinstall/check_pcr7.go @@ -113,18 +113,11 @@ func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context, // TODO(chrisccoulson): Not sure if there's any indication that we might get SPDM related measurements, // which our profile generation for PCR7 currently doesn't support. - // Obtain the BootCurrent variable and use this to obtain the corresponding load option - // that was measured to the log. BootXXXX variables are measured to the TPM and so we don't - // need to read back from an EFI variable that could have been modified between boot time - // and now. We need this so that we can identify the launch of the initial boot loader later - // on. This uses the same code that we use for PCR4 checks. - current, err := efi.ReadBootCurrentVariable(varCtx) + // Obtain the load option for the current boot. We need this so that we can identify the launch of + // the initial boot loader later on. + bootOpt, err := readCurrentBootLoadOptionFromLog(varCtx, log) if err != nil { - return nil, fmt.Errorf("cannot read BootCurrent variable: %w", err) - } - bootOpt, err := readLoadOptionFromLog(log, current) - if err != nil { - return nil, fmt.Errorf("cannot read current Boot%04x load option from log: %w", current, err) + return nil, err } // Make sure that the secure boot config in the log is measured in the diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index bf4cecb8..ad34d630 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -74,6 +74,7 @@ var ( DetermineCPUVendor = determineCPUVendor IsLaunchedFromLoadOption = isLaunchedFromLoadOption OpenAndCheckTPM2Device = openAndCheckTPM2Device + ReadCurrentBootLoadOptionFromLog = readCurrentBootLoadOptionFromLog ReadIntelHFSTSRegistersFromMEISysfs = readIntelHFSTSRegistersFromMEISysfs ReadIntelMEVersionFromMEISysfs = readIntelMEVersionFromMEISysfs ReadLoadOptionFromLog = readLoadOptionFromLog diff --git a/efi/preinstall/load_option_util.go b/efi/preinstall/load_option_util.go new file mode 100644 index 00000000..764e0e23 --- /dev/null +++ b/efi/preinstall/load_option_util.go @@ -0,0 +1,100 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall + +import ( + "bytes" + "context" + "errors" + "fmt" + "strings" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/tcglog-parser" + internal_efi "github.com/snapcore/secboot/internal/efi" +) + +// readLoadOptionFromLog reads the corresponding Boot#### load option from the log, +// which reflects the value of it at boot time, as opposed to reading it from an +// EFI variable which may have been modified since booting. +func readLoadOptionFromLog(log *tcglog.Log, n uint16) (*efi.LoadOption, error) { + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.PlatformConfigPCR { + continue + } + + if ev.EventType != tcglog.EventTypeEFIVariableBoot && ev.EventType != tcglog.EventTypeEFIVariableBoot2 { + // not a boot variable + continue + } + + data, ok := ev.Data.(*tcglog.EFIVariableData) + if !ok { + // decode error data is guaranteed to implement the error interface + return nil, fmt.Errorf("boot variable measurement has wrong data format: %w", ev.Data.(error)) + } + if data.VariableName != efi.GlobalVariable { + // not a global variable + continue + } + if !strings.HasPrefix(data.UnicodeName, "Boot") || len(data.UnicodeName) != 8 { + // name has unexpected prefix or length + continue + } + + var x uint16 + if c, err := fmt.Sscanf(data.UnicodeName, "Boot%x", &x); err != nil || c != 1 { + continue + } + if x != n { + // wrong load option + continue + } + + // We've found the correct load option. Decode it from the data stored in the log. + opt, err := efi.ReadLoadOption(bytes.NewReader(data.VariableData)) + if err != nil { + return nil, fmt.Errorf("cannot read load option from event data: %w", err) + } + return opt, nil + } + + return nil, errors.New("cannot find specified boot option") +} + +// readCurrentBootLoadOptionFromLog reads the load option associated with the current boot. +// It reads the BootCurrent global EFI variable and then looks up the corresponding BootXXXX +// entry that was measured to the TPM and present in the log, as BootXXXX variables are mutable +// and could have been modified between boot time and now. +func readCurrentBootLoadOptionFromLog(ctx context.Context, log *tcglog.Log) (*efi.LoadOption, error) { + current, err := efi.ReadBootCurrentVariable(ctx) + if err != nil { + return nil, fmt.Errorf("cannot read BootCurrent variable: %w", err) + } + opt, err := readLoadOptionFromLog(log, current) + if err != nil { + return nil, fmt.Errorf("cannot read current Boot%04X load option from log: %w", current, err) + } + return opt, nil +} diff --git a/efi/preinstall/load_option_util_test.go b/efi/preinstall/load_option_util_test.go new file mode 100644 index 00000000..daf10516 --- /dev/null +++ b/efi/preinstall/load_option_util_test.go @@ -0,0 +1,157 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall_test + +import ( + "context" + "errors" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/tcglog-parser" + . "github.com/snapcore/secboot/efi/preinstall" + internal_efi "github.com/snapcore/secboot/internal/efi" + "github.com/snapcore/secboot/internal/efitest" + "github.com/snapcore/secboot/internal/testutil" + + . "gopkg.in/check.v1" +) + +type loadOptionUtilSuite struct{} + +var _ = Suite(&loadOptionUtilSuite{}) + +func (s *loadOptionUtilSuite) TestReadLoadOptionFromLog(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{}) + opt, err := ReadLoadOptionFromLog(log, 3) + c.Assert(err, IsNil) + c.Check(opt, DeepEquals, &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + OptionalData: []byte{}, + }) +} + +func (s *loadOptionUtilSuite) TestReadLoadOptionFromLogNotExist(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{}) + _, err := ReadLoadOptionFromLog(log, 10) + c.Check(err, ErrorMatches, `cannot find specified boot option`) +} + +func (s *loadOptionUtilSuite) TestReadLoadOptionFromLogInvalidData(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.PlatformConfigPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableBoot { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + if data.UnicodeName != "Boot0003" { + continue + } + ev.Data = &invalidEventData{errors.New("some error")} + } + _, err := ReadLoadOptionFromLog(log, 3) + c.Check(err, ErrorMatches, `boot variable measurement has wrong data format: some error`) +} + +func (s *loadOptionUtilSuite) TestReadLoadOptionFromLogInvalidVariableName(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{}) + for _, ev := range log.Events { + if ev.PCRIndex != internal_efi.PlatformConfigPCR { + continue + } + if ev.EventType != tcglog.EventTypeEFIVariableBoot { + continue + } + data, ok := ev.Data.(*tcglog.EFIVariableData) + c.Assert(ok, testutil.IsTrue) + data.VariableName = efi.MakeGUID(0x6be4d043, 0x2ded, 0x4669, 0xa43b, [...]byte{0x91, 0x37, 0xb8, 0xa9, 0xd1, 0xa4}) + } + _, err := ReadLoadOptionFromLog(log, 3) + c.Check(err, ErrorMatches, `cannot find specified boot option`) +} + +func (s *loadOptionUtilSuite) TestReadCurrentBootLoadOptionFromLog(c *C) { + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{})), + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + }), + ) + + log, err := env.ReadEventLog() + c.Assert(err, IsNil) + + opt, err := ReadCurrentBootLoadOptionFromLog(env.VarContext(context.Background()), log) + c.Assert(err, IsNil) + c.Check(opt, DeepEquals, &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + OptionalData: []byte{}, + }) +} + +func (s *loadOptionUtilSuite) TestReadCurrentBootLoadOptionFromLogMissingBootCurrent(c *C) { + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{})), + ) + + log, err := env.ReadEventLog() + c.Assert(err, IsNil) + + _, err = ReadCurrentBootLoadOptionFromLog(env.VarContext(context.Background()), log) + c.Check(err, ErrorMatches, `cannot read BootCurrent variable: variable does not exist`) +} + +func (s *loadOptionUtilSuite) TestReadCurrentBootLoadOptionFromLogInvalidBootCurrent(c *C) { + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{})), + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0xA, 0x0}}, + }), + ) + + log, err := env.ReadEventLog() + c.Assert(err, IsNil) + + _, err = ReadCurrentBootLoadOptionFromLog(env.VarContext(context.Background()), log) + c.Check(err, ErrorMatches, `cannot read current Boot000A load option from log: cannot find specified boot option`) +} From 942ebac1cb0221aa85965ed6d8f57dd3aec4fdb6 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 15 Oct 2024 10:39:31 +0100 Subject: [PATCH 4/6] preinstall: move a deeply nested switch into its own function --- efi/preinstall/check_pcr7.go | 121 ++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go index adfabc6f..4c5c0a9f 100644 --- a/efi/preinstall/check_pcr7.go +++ b/efi/preinstall/check_pcr7.go @@ -41,6 +41,70 @@ var ( peNewFile = pe.NewFile ) +// checSucureBootVariableData checks the variable data associated with a configuration +// measurement. For "SecureBoot", it just ensures it contains 0x1. For PK, it makes sure +// it contains only a single X.509 signature. For the other veriables, it makes sure that +// the signature databases decode properly. +// +// If the supplied parameter is for a signature database, the decoded signature database +// is returned, else nil is returned +func checkSecureBootVariableData(data *tcglog.EFIVariableData) (sigDb efi.SignatureDatabase, err error) { + switch data.UnicodeName { + case "SecureBoot": + // Make sure the SecureBoot value in the log matches the EFI variable, + // (ie, []byte{1}). We don't do this for other variables because they can + // be updated from the OS, making them potentially inconsistent. The + // SecureBoot variable is read only after ExitBootServices. + if !bytes.Equal(data.VariableData, []byte{1}) { + return nil, errors.New("SecureBoot variable is not consistent with the corresponding EV_EFI_VARIABLE_DRIVER_CONFIG event value in the TCG log") + } + case "PK": + // Make sure that we can parse the PK database and it contains a single + // X.509 entry. + sigDb, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) + if err != nil { + return nil, fmt.Errorf("cannot decode PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event data: %w", err) + } + switch len(sigDb) { + case 0: + // This should never be empty when secure boot is enabled, + // so if it does then the firmware is broken. + return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: no signature list when secure boot is enabled") + case 1: + // PK only contains one ESL with the type EFI_CERT_X509_GUID + esl := sigDb[0] + if esl.Type != efi.CertX509Guid { + // PK can only contain a X.509 certificate. If we get another + // type then the firmwar is broken. + return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list has an unexpected type: %v", esl.Type) + } + if len(esl.Signatures) != 1 { + // EFI_CERT_X509_GUID signature lists can only contain a single + // signature. Note that it's quite likely that go-efilib would have + // failed to decode already in this case because all signatures + // within a signature list have to be the same size. + // + // In any case, if this happens the firmware is broken. + return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list should only have one signature, but got %d", len(esl.Signatures)) + } + if _, err := x509.ParseCertificate(esl.Signatures[0].Data); err != nil { + return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: cannot decode PK certificate: %w", err) + } + default: + // If PK contains more than 1 ESL, then the firmware is broken. + return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: more than one signature list is present") + } + default: + // Make sure that we can parse all other signature databases ok + sigDb, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) + if err != nil { + return nil, fmt.Errorf("cannot decode %s contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", data.UnicodeName, err) + } + } + + return sigDb, nil +} + type secureBootPolicyResultFlags int const ( @@ -188,53 +252,12 @@ NextEvent: data.UnicodeName, data.VariableName, expectedDigest, ev.Digests[pcrAlg]) } - switch data.UnicodeName { - case "SecureBoot": - // Make sure the SecureBoot value in the log matches the EFI variable, - // (ie, []byte{1}). We don't do this for other variables because they can - // be updated from the OS, making them potentially inconsistent. The - // SecureBoot variable is read only after ExitBootServices. - if !bytes.Equal(data.VariableData, []byte{1}) { - return nil, errors.New("SecureBoot variable is not consistent with the corresponding EV_EFI_VARIABLE_DRIVER_CONFIG event value in the TCG log") - } - case "PK": - // Make sure that we can parse the PK database and it contains a single - // X.509 entry. - pk, err := efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) - if err != nil { - return nil, fmt.Errorf("cannot decode PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event data: %w", err) - } - switch len(pk) { - case 0: - // This should never be empty when secure boot is enabled, - // so if it does then the firmware is broken. - return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: no signature list when secure boot is enabled") - case 1: - esl := pk[0] - if esl.Type != efi.CertX509Guid { - // PK can only contain a X.509 certificate. If we get another - // type then the firmwar is broken. - return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list has an unexpected type: %v", esl.Type) - } - if len(esl.Signatures) != 1 { - // EFI_CERT_X509_GUID signature lists can only contain a single - // signature. If there isn't then the firmware is broken. - return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list should only have one signature, but got %d", len(esl.Signatures)) - } - if _, err := x509.ParseCertificate(esl.Signatures[0].Data); err != nil { - return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: cannot decode PK certificate: %w", err) - } - default: - // If PK contains more than 1 ESL, then the firmware is broken. - return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: more than one signature list is present") - } - case "db": + sigDb, err := checkSecureBootVariableData(data) + if err != nil { + return nil, err + } + if data.UnicodeName == "db" { // Capture the db from the log for future use. - var err error - db, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) - if err != nil { - return nil, fmt.Errorf("cannot decode db contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", err) - } // We don't check the EFI_SIGNATURE_LIST types contained in db. Any OS component with a valid // Authenticode signature (WIN_CERT_TYPE_PKCS_SIGNED_DATA) or a valid PKCS7 signature // (WIN_CERT_TYPE_EFI_GUID with the type EFI_CERT_TYPE_PKCS7_GUID) is authenticated with @@ -244,11 +267,7 @@ NextEvent: // these are only used to authenticate unsigned images (which the profile generation in the // secboot efi package rejects) or as a fallback for signed image where signature verification // fails. Digests may be permitted for authenticating unsigned pre-OS components. - default: - // Make sure that we can parse all other signature databases ok - if _, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)); err != nil { - return nil, fmt.Errorf("cannot decode %s contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", data.UnicodeName, err) - } + db = sigDb } case tcglog.EventTypeEFIAction: // This branch exists here for documentation purposes - it falls through to the From 5655f3faf09ba77dbe635185b13ae80fdcfe4265 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 15 Oct 2024 14:00:05 +0100 Subject: [PATCH 5/6] preinstall: move a shared function into another file + extra cleanups --- efi/preinstall/check_pcr4.go | 78 +---- efi/preinstall/check_pcr4_test.go | 372 ---------------------- efi/preinstall/check_pcr7.go | 10 +- efi/preinstall/check_pcr7_test.go | 2 +- efi/preinstall/load_option_util.go | 67 ++++ efi/preinstall/load_option_util_test.go | 395 ++++++++++++++++++++++++ 6 files changed, 471 insertions(+), 453 deletions(-) diff --git a/efi/preinstall/check_pcr4.go b/efi/preinstall/check_pcr4.go index 7054038f..408c0e5f 100644 --- a/efi/preinstall/check_pcr4.go +++ b/efi/preinstall/check_pcr4.go @@ -37,68 +37,6 @@ var ( efiComputePeImageDigest = efi.ComputePeImageDigest ) -// isLaunchedFromLoadOption returns true if the supplied EV_EFI_BOOT_SERVICES_APPLICATION event -// is associated with the supplied load option. This will panic if the event is of the -// wrong type or the event data decodes incorrectly. This works by doing a device path match, -// which can either be a full match, or a recognized short-form match. This also handles the case -// where the boot option points to a removable device and the executable associated with the load -// event is loaded from that device. -func isLaunchedFromLoadOption(ev *tcglog.Event, opt *efi.LoadOption) (yes bool, err error) { - if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { - // The caller should check this. - panic("unexpected event type") - } - - // Grab the device path from the event. For the launch of the initial boot loader, this - // will always be a full path. - eventDevicePath := ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath - if len(eventDevicePath) == 0 { - return false, errors.New("EV_EFI_BOOT_SERVICES_APPLICATION event has empty device path") - } - - // Try to match the load option. - if opt.Attributes&efi.LoadOptionActive == 0 { - // the load option isn't active. - return false, errors.New("boot option is not active") - } - - // Test to see if the load option path matches the load event path in some way. Note - // that the load option might be in short-form, but this function takes that into - // account. - if eventDevicePath.Matches(opt.FilePath) != efi.DevicePathNoMatch { - // We have a match. This is very likely to be a launch of the - // load option. - return true, nil - } - - // There's no match with the load option. This might happen when booting from - // removable media where the load option specifies the device path pointing to - // the bus that the removable media is connected to, but the load event contains - // the full path to the initial boot loader, using some extra components. - // Unless the load option is already using a short-form path, try appending the - // extra components for the removable media from the load event to the load option - // path and try testing for a match again. - if opt.FilePath.ShortFormType().IsShortForm() { - // The load option path is in short-form. We aren't going to find a match. - return false, nil - } - - // Copy the load option path - optFilePath := append(efi.DevicePath{}, opt.FilePath...) - if cdrom := efi.DevicePathFindFirstOccurrence[*efi.CDROMDevicePathNode](eventDevicePath); len(cdrom) > 0 { - // Booting from CD-ROM. - optFilePath = append(optFilePath, cdrom...) - } else if hd := efi.DevicePathFindFirstOccurrence[*efi.HardDriveDevicePathNode](eventDevicePath); len(hd) > 0 { - // Booting from any removable device with a GPT, such as a USB drive. - optFilePath = append(optFilePath, hd...) - } - - // With the CDROM() or HD() components of the event file path appended to the - // load option path, test for a match again. In this case, we expect a full - // match as neither paths are in short-form. - return eventDevicePath.Matches(optFilePath) == efi.DevicePathFullMatch, nil -} - type bootManagerCodeResultFlags int const ( @@ -289,21 +227,13 @@ NextEvent: continue NextEvent } - data, eventDataOk := ev.Data.(*tcglog.EFIImageLoadEvent) - switch seenOSComponentLaunches { case 0: - if !eventDataOk { - // Only require the event data to be ok for firmware generated events. This is because - // OS components might create invalid data (and shim actually does), so we ignore those - // errors. - return 0, fmt.Errorf("invalid OS-present EV_EFI_BOOT_SERVICES_APPLICATION event data: %w", ev.Data.(error)) - } // Check if this launch is associated with the EFI_LOAD_OPTION associated with - // the current boot. + // the current boot. This will fail if the data associated with the event is invalid. isBootOptLaunch, err := isLaunchedFromLoadOption(ev, bootOpt) if err != nil { - return 0, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is associated with the current boot load option: %w", data.DevicePath, err) + return 0, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event is associated with the current boot load option: %w", err) } if isBootOptLaunch { // We have the EV_EFI_BOOT_SERVICES_APPLICATION event associated with the IBL launch. @@ -311,13 +241,15 @@ NextEvent: } else { // We have an EV_EFI_BOOT_SERVICES_APPLICATION that didn't come from the load option // associated with the current boot. - // Test to see if it's part of Absolute. If it is, that's fine - we copy this into + // Test to see if it's part of Absolute. I it is, that's fine - we copy this into // the profile, so we don't need to do any other verification of it and we don't have // anything to verify the Authenticode digest against anyway. We have a device path, // but not one that we're able to read back from. // // If this isn't Absolute, we bail with an error. We don't support anything else being // loaded here, and ideally Absolute will be turned off as well. + + data := ev.Data.(*tcglog.EFIImageLoadEvent) // this is safe, else the earlier isLaunchedFromLoadOption would have returned an error if result&bootManagerCodeAbsoluteComputraceRunning > 0 { return 0, fmt.Errorf("OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is not associated with the current boot load option and is not Absolute", data.DevicePath) } diff --git a/efi/preinstall/check_pcr4_test.go b/efi/preinstall/check_pcr4_test.go index a28ca020..17d5425e 100644 --- a/efi/preinstall/check_pcr4_test.go +++ b/efi/preinstall/check_pcr4_test.go @@ -39,378 +39,6 @@ type pcr4Suite struct{} var _ = Suite(&pcr4Suite{}) -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGood(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x0}, - &efi.NVMENamespaceDevicePathNode{ - NamespaceID: 0x1, - NamespaceUUID: 0x0}, - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x0}, - &efi.NVMENamespaceDevicePathNode{ - NamespaceID: 0x1, - NamespaceUUID: 0x0}, - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - }, - } - - yes, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, IsNil) - c.Check(yes, testutil.IsTrue) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGoodShortFormOpt(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x0}, - &efi.NVMENamespaceDevicePathNode{ - NamespaceID: 0x1, - NamespaceUUID: 0x0}, - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - }, - } - - yes, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, IsNil) - c.Check(yes, testutil.IsTrue) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGoodRemovableCDROM(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1}, - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1}, - &efi.CDROMDevicePathNode{ - BootEntry: 0, - PartitionStart: 0x800, - PartitionSize: 0x100000}, - efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), - }, - }, - } - - yes, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, IsNil) - c.Check(yes, testutil.IsTrue) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGoodRemovableUSB(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x8}, - &efi.USBDevicePathNode{ - ParentPortNumber: 2, - InterfaceNumber: 0}, - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x8}, - &efi.USBDevicePathNode{ - ParentPortNumber: 2, - InterfaceNumber: 0}, - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), - }, - }, - } - - yes, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, IsNil) - c.Check(yes, testutil.IsTrue) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNoMatch(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x1e482b5b, 0x6600, 0x427f, 0xb394, [...]uint8{0x9a, 0x68, 0x82, 0x3e, 0x55, 0x04})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1}, - &efi.CDROMDevicePathNode{ - BootEntry: 0, - PartitionStart: 0x800, - PartitionSize: 0x100000}, - efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), - }, - }, - } - - yes, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, IsNil) - c.Check(yes, testutil.IsFalse) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNoMatchRemovable(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x8}, - &efi.USBDevicePathNode{ - ParentPortNumber: 2, - InterfaceNumber: 0}, - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x0}, - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), - }, - }, - } - - yes, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, IsNil) - c.Check(yes, testutil.IsFalse) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionEmptyDevicePath(c *C) { - opt := &efi.LoadOption{ - Attributes: 1, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{}, - }, - } - - _, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, ErrorMatches, `EV_EFI_BOOT_SERVICES_APPLICATION event has empty device path`) -} - -func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNotActive(c *C) { - opt := &efi.LoadOption{ - Attributes: 0, - Description: "ubuntu", - FilePath: efi.DevicePath{ - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - } - ev := &tcglog.Event{ - PCRIndex: internal_efi.BootManagerCodePCR, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: &tcglog.EFIImageLoadEvent{ - LocationInMemory: 0x6556c018, - LengthInMemory: 955072, - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x1d}, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x0}, - &efi.NVMENamespaceDevicePathNode{ - NamespaceID: 0x1, - NamespaceUUID: 0x0}, - &efi.HardDriveDevicePathNode{ - PartitionNumber: 1, - PartitionStart: 0x800, - PartitionSize: 0x100000, - Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), - MBRType: efi.GPT}, - efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), - }, - }, - } - - _, err := IsLaunchedFromLoadOption(ev, opt) - c.Check(err, ErrorMatches, `boot option is not active`) -} - type testCheckBootManagerCodeMeasurementsParams struct { env internal_efi.HostEnvironment pcrAlg tpm2.HashAlgorithmId diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go index 4c5c0a9f..27572ff0 100644 --- a/efi/preinstall/check_pcr7.go +++ b/efi/preinstall/check_pcr7.go @@ -307,14 +307,9 @@ NextEvent: // and we haven't seen the event for the IBL yet. We stop once we see this // because at this point, the rest of the measurements in this PCR are under // the control of the OS. - data, ok := ev.Data.(*tcglog.EFIImageLoadEvent) - if !ok { - return nil, fmt.Errorf("invalid OS-present EV_EFI_BOOT_SERVICES_APPLICATION event data: %w", ev.Data.(error)) - } - yes, err := isLaunchedFromLoadOption(ev, bootOpt) if err != nil { - return nil, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is associated with the current boot load option: %w", data.DevicePath, err) + return nil, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for is associated with the current boot load option: %w", err) } if !yes { // This is not the launch event for the initial boot loader - ignore it. @@ -328,7 +323,8 @@ NextEvent: // other Flash volumes), then we'll generate a potentially invalid // profile for PCR7, because we don't copy events from the log once // we're in OS-present. - return nil, fmt.Errorf("unexpected EV_EFI_BOOT_SERVICES_APPLICATION event for %v after already seeing a verification event during the OS-present environment. This event should be for the initial boot loader", data.DevicePath) + return nil, fmt.Errorf("unexpected EV_EFI_BOOT_SERVICES_APPLICATION event for %v after already seeing a verification event during the OS-present environment. "+ + "This event should be for the initial boot loader", ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath) } continue NextEvent } diff --git a/efi/preinstall/check_pcr7_test.go b/efi/preinstall/check_pcr7_test.go index 02aea183..16315446 100644 --- a/efi/preinstall/check_pcr7_test.go +++ b/efi/preinstall/check_pcr7_test.go @@ -955,7 +955,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, ErrorMatches, `invalid OS-present EV_EFI_BOOT_SERVICES_APPLICATION event data: some error`) + c.Check(err, ErrorMatches, `cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for is associated with the current boot load option: event has invalid event data: some error`) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidVerificationEventData(c *C) { diff --git a/efi/preinstall/load_option_util.go b/efi/preinstall/load_option_util.go index 764e0e23..fd66ac46 100644 --- a/efi/preinstall/load_option_util.go +++ b/efi/preinstall/load_option_util.go @@ -98,3 +98,70 @@ func readCurrentBootLoadOptionFromLog(ctx context.Context, log *tcglog.Log) (*ef } return opt, nil } + +// isLaunchedFromLoadOption returns true if the supplied EV_EFI_BOOT_SERVICES_APPLICATION event +// is associated with the supplied load option. This load option should be the one for the current +// boot (eg, the result of readCurrentBootLoadOptionFromLog). This works by doing a device path match, +// which can either be a full match, or a recognized short-form match. This also handles the case +// where the boot option points to a removable device and the executable associated with the load +// event is loaded from that device. +func isLaunchedFromLoadOption(ev *tcglog.Event, opt *efi.LoadOption) (yes bool, err error) { + if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { + // The caller should check this. + return false, fmt.Errorf("expected EV_EFI_BOOT_SERVICES_APPLICATION event, got %v", ev.EventType) + } + + data, ok := ev.Data.(*tcglog.EFIImageLoadEvent) + if !ok { + return false, fmt.Errorf("event has invalid event data: %w", ev.Data.(error)) + } + + // Grab the device path from the event. For the launch of the initial boot loader, this + // will always be a full path. + eventDevicePath := data.DevicePath + if len(eventDevicePath) == 0 { + return false, errors.New("event has empty device path") + } + + // Try to match the load option. + if opt.Attributes&efi.LoadOptionActive == 0 { + // the load option isn't active. + return false, errors.New("boot option is not active") + } + + // Test to see if the load option path matches the load event path in some way. Note + // that the load option might be in short-form, but this function takes that into + // account. + if eventDevicePath.Matches(opt.FilePath) != efi.DevicePathNoMatch { + // We have a match. This is very likely to be a launch of the + // load option. + return true, nil + } + + // There's no match with the load option. This might happen when booting from + // removable media where the load option specifies the device path pointing to + // the bus that the removable media is connected to, but the load event contains + // the full path to the initial boot loader, using some extra components. + // Unless the load option is already using a short-form path, try appending the + // extra components for the removable media from the load event to the load option + // path and try testing for a match again. + if opt.FilePath.ShortFormType().IsShortForm() { + // The load option path is in short-form. We aren't going to find a match. + return false, nil + } + + // Copy the load option path + optFilePath := append(efi.DevicePath{}, opt.FilePath...) + if cdrom := efi.DevicePathFindFirstOccurrence[*efi.CDROMDevicePathNode](eventDevicePath); len(cdrom) > 0 { + // Booting from CD-ROM. + optFilePath = append(optFilePath, cdrom...) + } else if hd := efi.DevicePathFindFirstOccurrence[*efi.HardDriveDevicePathNode](eventDevicePath); len(hd) > 0 { + // Booting from any removable device with a GPT, such as a USB drive. + optFilePath = append(optFilePath, hd...) + } + + // With the CDROM() or HD() components of the event file path appended to the + // load option path, test for a match again. In this case, we expect a full + // match as neither paths are in short-form. + return eventDevicePath.Matches(optFilePath) == efi.DevicePathFullMatch, nil +} diff --git a/efi/preinstall/load_option_util_test.go b/efi/preinstall/load_option_util_test.go index daf10516..fba2a7d5 100644 --- a/efi/preinstall/load_option_util_test.go +++ b/efi/preinstall/load_option_util_test.go @@ -155,3 +155,398 @@ func (s *loadOptionUtilSuite) TestReadCurrentBootLoadOptionFromLogInvalidBootCur _, err = ReadCurrentBootLoadOptionFromLog(env.VarContext(context.Background()), log) c.Check(err, ErrorMatches, `cannot read current Boot000A load option from log: cannot find specified boot option`) } + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionGood(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsTrue) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionGoodShortFormOpt(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsTrue) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionGoodRemovableCDROM(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1}, + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1}, + &efi.CDROMDevicePathNode{ + BootEntry: 0, + PartitionStart: 0x800, + PartitionSize: 0x100000}, + efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsTrue) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionGoodRemovableUSB(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x8}, + &efi.USBDevicePathNode{ + ParentPortNumber: 2, + InterfaceNumber: 0}, + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x8}, + &efi.USBDevicePathNode{ + ParentPortNumber: 2, + InterfaceNumber: 0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsTrue) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionNoMatch(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x1e482b5b, 0x6600, 0x427f, 0xb394, [...]uint8{0x9a, 0x68, 0x82, 0x3e, 0x55, 0x04})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1}, + &efi.CDROMDevicePathNode{ + BootEntry: 0, + PartitionStart: 0x800, + PartitionSize: 0x100000}, + efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsFalse) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionNoMatchRemovable(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x8}, + &efi.USBDevicePathNode{ + ParentPortNumber: 2, + InterfaceNumber: 0}, + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsFalse) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionInvalidEventData(c *C) { + opt := &efi.LoadOption{ + Attributes: 0, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &invalidEventData{errors.New("some error")}, + } + _, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, ErrorMatches, `event has invalid event data: some error`) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionEmptyDevicePath(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{}, + }, + } + + _, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, ErrorMatches, `event has empty device path`) +} + +func (s *loadOptionUtilSuite) TestIsLaunchedFromLoadOptionNotActive(c *C) { + opt := &efi.LoadOption{ + Attributes: 0, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + }, + } + + _, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, ErrorMatches, `boot option is not active`) +} From c2816204808905e781578c538a503a7ef6e8f582 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 12 Nov 2024 23:27:34 +0000 Subject: [PATCH 6/6] preinstall: Refactor the PCR7 checks Hopefully this will address any outstanding review comments. --- efi/preinstall/check_pcr7.go | 525 ++++++++++++++++++++---------- efi/preinstall/check_pcr7_test.go | 10 +- 2 files changed, 355 insertions(+), 180 deletions(-) diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go index 27572ff0..5d22daad 100644 --- a/efi/preinstall/check_pcr7.go +++ b/efi/preinstall/check_pcr7.go @@ -41,7 +41,7 @@ var ( peNewFile = pe.NewFile ) -// checSucureBootVariableData checks the variable data associated with a configuration +// checkSecureBootVariableData checks the variable data associated with a configuration // measurement. For "SecureBoot", it just ensures it contains 0x1. For PK, it makes sure // it contains only a single X.509 signature. For the other veriables, it makes sure that // the signature databases decode properly. @@ -56,27 +56,27 @@ func checkSecureBootVariableData(data *tcglog.EFIVariableData) (sigDb efi.Signat // be updated from the OS, making them potentially inconsistent. The // SecureBoot variable is read only after ExitBootServices. if !bytes.Equal(data.VariableData, []byte{1}) { - return nil, errors.New("SecureBoot variable is not consistent with the corresponding EV_EFI_VARIABLE_DRIVER_CONFIG event value in the TCG log") + return nil, errors.New("SecureBoot value is not consistent with the current EFI variable value") } case "PK": // Make sure that we can parse the PK database and it contains a single // X.509 entry. sigDb, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) if err != nil { - return nil, fmt.Errorf("cannot decode PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event data: %w", err) + return nil, fmt.Errorf("cannot decode PK contents: %w", err) } switch len(sigDb) { case 0: // This should never be empty when secure boot is enabled, // so if it does then the firmware is broken. - return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: no signature list when secure boot is enabled") + return nil, errors.New("invalid PK contents: no signature list when secure boot is enabled") case 1: // PK only contains one ESL with the type EFI_CERT_X509_GUID esl := sigDb[0] if esl.Type != efi.CertX509Guid { // PK can only contain a X.509 certificate. If we get another // type then the firmwar is broken. - return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list has an unexpected type: %v", esl.Type) + return nil, fmt.Errorf("invalid PK contents: signature list has an unexpected type: %v", esl.Type) } if len(esl.Signatures) != 1 { // EFI_CERT_X509_GUID signature lists can only contain a single @@ -85,38 +85,291 @@ func checkSecureBootVariableData(data *tcglog.EFIVariableData) (sigDb efi.Signat // within a signature list have to be the same size. // // In any case, if this happens the firmware is broken. - return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: signature list should only have one signature, but got %d", len(esl.Signatures)) + return nil, fmt.Errorf("invalid PK contents: signature list should only have one signature, but got %d", len(esl.Signatures)) } if _, err := x509.ParseCertificate(esl.Signatures[0].Data); err != nil { - return nil, fmt.Errorf("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: cannot decode PK certificate: %w", err) + return nil, fmt.Errorf("invalid PK contents: cannot decode PK certificate: %w", err) } default: // If PK contains more than 1 ESL, then the firmware is broken. - return nil, errors.New("invalid PK contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: more than one signature list is present") + return nil, errors.New("invalid PK contents: more than one signature list is present") } default: // Make sure that we can parse all other signature databases ok sigDb, err = efi.ReadSignatureDatabase(bytes.NewReader(data.VariableData)) if err != nil { - return nil, fmt.Errorf("cannot decode %s contents from EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", data.UnicodeName, err) + return nil, fmt.Errorf("cannot decode %s contents: %w", data.UnicodeName, err) } } return sigDb, nil } +// checkX509CertificatePublicKeyStrength checks whether the supplied certificate's +// public key is considered strong enough for signing. This will return true if it is +// or false if it isn't. It will return an error for unsupported public key algorithms +// or if the public key's concrete type is inconsistent with the algorithm. +func checkX509CertificatePublicKeyStrength(cert *x509.Certificate) (ok bool, err error) { + switch cert.PublicKeyAlgorithm { + case x509.RSA: + pubKey, isRsa := cert.PublicKey.(*rsa.PublicKey) + if !isRsa { + return false, errors.New("unsupported public key type") + } + if pubKey.Size() < 256 { + // Anything less than 2048-bits is considered weak + return false, nil + } + default: + // EFI implementations aren't required to support anything other + // than RSA. + return false, errors.New("unsupported public key algorithm") + } + + return true, nil +} + +// checkSignatureDataStrength will check if the signature data of the supplied type is +// strong enough for authenticating images. This will return true if it is or false if +// it isn't. +func checkSignatureDataStrength(eslType efi.GUID, esdData []byte) (ok bool, err error) { + switch eslType { + case efi.CertX509Guid: + cert, err := x509.ParseCertificate(esdData) + if err != nil { + return false, fmt.Errorf("cannot decode certificate: %w", err) + } + return checkX509CertificatePublicKeyStrength(cert) + case efi.CertSHA1Guid: + return false, nil + case efi.CertSHA224Guid, efi.CertSHA256Guid, efi.CertSHA384Guid, efi.CertSHA512Guid: + return true, nil + default: + return false, fmt.Errorf("unrecognized signature type: %v", eslType) + } +} + +var errNoSignerWithTrustAnchor = errors.New("image has no signer associated with any of the supplied authorities") + +// extractSignerWithTrustAnchorFromImage extracts and returns the signing certificate from any +// signature where the signer chains to one of the supplied authorities. As with signature +// verification in EFI, it tests each of the image's signatures against each of the supplied +// authorities in turn, and will return the first signature that chains to the first authority. +func extractSignerWithTrustAnchorFromImage(authorities []*x509.Certificate, image secboot_efi.Image) (*x509.Certificate, error) { + r, err := image.Open() + if err != nil { + return nil, fmt.Errorf("cannot open image: %w", err) + } + defer r.Close() + + pefile, err := peNewFile(r) + if err != nil { + return nil, fmt.Errorf("cannot decode image: %w", err) + } + + sigs, err := internal_efiSecureBootSignaturesFromPEFile(pefile, r) + if err != nil { + return nil, fmt.Errorf("cannot obtain secure boot signatures from image %s: %w", image, err) + } + + // Make sure that one of the CA's measured for verification so far + // is a trust anchor for one of the signatures on the image. + var foundSig *efi.WinCertificateAuthenticode + for _, cert := range authorities { + for _, sig := range sigs { + if sig.CertLikelyTrustAnchor(cert) { + foundSig = sig + break + } + } + if foundSig != nil { + break + } + } + if foundSig == nil { + return nil, errNoSignerWithTrustAnchor + } + + return foundSig.GetSigner(), nil +} + +// handleVariableAuthority event processes the event data for the supplied EV_EFI_VARIABLE_AUTHORITY +// event. It expects the authority to be the UEFI db, and if verifyEventDigest is true, it expects +// the digests associated with pcrAlg to match the digest computed from the event data. It will return +// an error if the digest already appears in the provided alreadyMeasured argument, as the firmware +// should only measure a digest once. It uses the supplied db to match the EFI_SIGNATURE_DATA in the +// event data to a EFI_SIGNATURE_LIST, in order to obtain the signature type. On success, the function +// returns the signature type and signature data (without the owner). The caller should subsequently add +// the measurement digest to the alreadyMeasured slice. +func handleVariableAuthorityEvent(pcrAlg tpm2.HashAlgorithmId, db efi.SignatureDatabase, alreadyMeasured tpm2.DigestList, ev *tcglog.Event, verifyEventDigest bool) (eslType efi.GUID, esdData []byte, err error) { + // Decode the verification event + data, ok := ev.Data.(*tcglog.EFIVariableData) + if !ok { + // if decoding failed, the resulting data is guaranteed to implement error. + return efi.GUID{}, nil, fmt.Errorf("event has wong data format: %w", ev.Data.(error)) + } + + // As we're only checking events up to the launch of the IBL, we don't expect + // to see anything other than verification events from db here. + if data.VariableName != efi.ImageSecurityDatabaseGuid || data.UnicodeName != "db" { + return efi.GUID{}, nil, fmt.Errorf("event is not from db (got %s-%v)", data.UnicodeName, data.VariableName) + } + + if verifyEventDigest { + expectedDigest := tcglog.ComputeEFIVariableDataDigest(pcrAlg.GetHash(), data.UnicodeName, data.VariableName, data.VariableData) + if !bytes.Equal(ev.Digests[pcrAlg], expectedDigest) { + return efi.GUID{}, nil, fmt.Errorf("event data inconsistent with %v event digest (log digest:%#x, expected digest:%#x)", pcrAlg, ev.Digests[pcrAlg], expectedDigest) + } + } + + // Make sure that this signature hasn't already been measured. Duplicate signatures measured + // by the firmware may result in incorrectly computed PCR policies. + // Unfortunately, this test isn't 100% reliable as we stop processing events after the launch + // of the IBL (usually shim). Once the IBL has launched, we can't tell whether subsequent events + // were generated by the firmware because an OS component made use of LoadImage (where we would + // want to make sure it isn't measured again) or whether subsequent events are measured via some + // other mechanism by an OS component, such as the shim verification (which we wouldn't want to + // check, because we're only testing firmware compatbility here). I can't think of a way to make + // this 100% reliable other than by ensuring OS components never measure events with "db" and + // IMAGE_SECURITY_DATABASE_GUID in their event data, as a way of being able to distinguish + // firmware generated events from OS component generated events. It's a legitimate scenario for + // both the firmware and shim to both measure the same signature they used for verification from db + // because they both maintain their own de-duplication lists. + // + // If this test fails, the firmware is definitely broken. If this test doesn't fail, the opposite is + // not true - it's not a definitive guarantee that the firmware isn't broken, unfortunately. + for _, measured := range alreadyMeasured { + if bytes.Equal(measured, ev.Digests[pcrAlg]) { + return efi.GUID{}, nil, fmt.Errorf("digest %#x has been measured by the firmware already", ev.Digests[pcrAlg]) + } + } + + // Try to discover the type of authentication. The measured EFI_SIGNATURE_DATA doesn't + // contain this. + + // First of all, construct a signature data entry from the raw event data. + esd := new(efi.SignatureData) + r := bytes.NewReader(data.VariableData) + + // THE EFI_SIGNATURE_DATA entry starts with the owner GUID + sigOwner, err := efi.ReadGUID(r) + if err != nil { + return efi.GUID{}, nil, fmt.Errorf("cannot decode owner GUID from event: %w", err) + } + esd.Owner = sigOwner + + // The rest of the EFI_SIGNATURE_DATA entry is the data + sigData, err := io.ReadAll(r) + if err != nil { + return efi.GUID{}, nil, fmt.Errorf("cannot read data from event: %w", err) + } + esd.Data = sigData + + // We have a fully constructed EFI_SIGNATURE_DATA. Now iterate over db to see if this + // EFI_SIGNATURE_DATA belongs to any EFI_SIGNATURE_LIST, in order to grab its type. + var matchedEsl *efi.SignatureList + for _, list := range db { + for _, sig := range list.Signatures { + if sig.Equal(esd) { + matchedEsl = list + break + } + } + if matchedEsl != nil { + break + } + } + if matchedEsl == nil { + return efi.GUID{}, nil, fmt.Errorf("event does not match any db EFI_SIGNATURE_LIST") + } + + return matchedEsl.Type, esd.Data, nil +} + type secureBootPolicyResultFlags int const ( - secureBootIncludesWeakAlg secureBootPolicyResultFlags = 1 << iota - secureBootPreOSVerificationIncludesDigest + secureBootIncludesWeakAlg secureBootPolicyResultFlags = 1 << iota // Weak algorithms were used during image verification. + secureBootPreOSVerificationIncludesDigest // Authenticode digests were used to authenticate pre-OS components. ) +// secureBootPolicyResult is the result of a successful call to checkSecureBootPolicyMeasurementsAndObtainAuthorities. type secureBootPolicyResult struct { - UsedAuthorities []*x509.Certificate + UsedAuthorities []*x509.Certificate // CA's used to authenticate boot components. Flags secureBootPolicyResultFlags } +// checkSecureBootPolicyMeasurementsAndObtainAuthorities performs some checks on the secure boot policy PCR (7). + +// The supplied context is used to attach an EFI variable backend to, for functions that read +// from EFI variables. The supplied env and log arguments provide other inputs to this function. +// The pcrAlg argument is the PCR bank that is chosen as the best one to use. The iblImage +// corresponds to the initial boot loader image for the current boot. This is used to detect the +// launch of the OS, at which checks for PCR7 end. There are some limitations of this, ie, we may +// not detect LoadImage bugs that happen later on, but once the OS has loaded, it's impossible to +// tell whicj events come from firmware and which are under the control of OS components. + +// This ensures that secure boot is enabled, else an error is returned, as WithSecureBootPolicyProfile +// only generates profiles compatible with secure boot being enabled. + +// If the version of UEFI is >= 2.5, it also makes sure that the secure boot mode is "deployed mode". +// If the secure boot mode is "user mode", then the "AuditMode" and "DeployedMode" values are measured to PCR7, +// something that WithSecureBootPolicyProfile doesn't support today. Support for "user mode" will be added +// in the future, although the public RunChecks API will probably require a flag to opt in to supporting user +// mode, as it is the less secure mode of the 2 (see the documentation for SecureBootMode in +// github.com/canonical/go-efilib). + +// It also reads the "OsIndicationsSupported" variable to test for features that are not supported by +// WithSecureBootPolicyProfile. These are timestamp revocation (which requires an extra signature database - +// "dbt") and OS recovery (which requires an extra signature database -"dbr", used to control access to +// OsRecoveryOrder and OsRecover#### variables). Of the 2, it's likely that we might need to add support for +// timestamp revocation at some point in the future. + +// It reads the "BootCurrent" EFI variable and matches this to the EFI_LOAD_OPTION associated with the current +// boot from the TCG log - it uses the log as "BootXXXX" EFI variables can be updated at runtime and +// might be out of data when this code runs. It uses this to detect the launch of the initial boot loader, +// which might not necessarily be the first EV_EFI_BOOT_SERVICES_APPLICATION event in the OS-present +// environment in PCR4 (eg, if Absolute is active). + +// After these checks, it iterates over the secure boot configuration in the log, making sure that the +// configuration is measured in the correct order, that the event data is valid, and that the measured digest +// is the tagged hash of the event data. It makes sure that the value of "SecureBoot" in the log is consistent +// with the "SecureBoot" variable (which is read-only at runtime), and it verifies that all of the signature +// databases are formatted correctly and can be decoded. It will return an error if any of these checks fail. + +// If the pre-OS environment contains events other than EV_EFI_VARIABLE_DRIVER_CONFIG, it will return an error. +// This can happen a firmware debugger is enabled, in which case PCR7 will begin with a EV_EFI_ACTION +// "UEFI Debug Mode" event. This case is detected by earlier firmware protection checks. + +// If not all of the expected secure boot configuration is measured, an error is returned. + +// Once the secure boot configuration has been measured, it looks for EV_EFI_VARIABLE_AUTHORITY events in PCR7, +// until it detects the launch of the initial boot loader. It verifies that each of these come from db, and +// if the log is in the OS-present environment, it ensures that the measured digest is the tagged hash of the +// event data. It doesn't do this for events in the pre-OS environment because WithSecureBootPolicyProfile +// just copies these to the profile. It verifies that the firmware doesn't measure a signature more than once. +// For each EV_EFI_VARIABLE_AUTHORITY event, it also matches the measured signature to a EFI_SIGNATURE_LIST +// structure in db. If the matched ESL is a X.509 certificate, it records the use of this CA in the return value. +// If the CA is an RSA certificate with a public modulus of < 256 bytes, it sets a flag in the return value +// indicating a weak algorithm. If the matched ESL is a Authenticode digest, it sets a flag in the return value +// indicating that pre-OS components were verified using digests rather than signatures. This only applies to the +// pre-OS environment and makes PCR7 fragile wrt firmware updates, because it means db needs to be updated to +// reflect the new components each time. If the digest being matched is SHA-1, it sets the flag in the return +// value indicating a weak algorithm. If any of these checks fail, an error is returned. If an event type +// other than EV_EFI_VARIABLE_AUTHORITY is detected, an error is returned. + +// Upon detecting the launch of the initial boot loader in PCR4, it extracts the authenticode signatures from +// the supplied image, and matches these to a previously measured CA. If no match is found, an error is returned. +// If a match is found, it ensures that the signing certificate has an RSA public key with a modulus that is at +// least 256 bytes, else it sets a flag in the return value indicating a weak algorithm. +// +// Once the event for the initial boot loader is complete, the function returns. It doesn't process any more +// EV_EFI_VARIABLE_AUTHORITY events because it's impossible to determine if these result from a call to the +// firmware's LoadImage API, or if they are logged by an OS component, both of which may maintain their own +// de-duplication lists (this is certainly the case for shim). Ideally, checking would continue but this trade +// off was made instead. +// +// If the end of the log is reached without encountering the launch of the initial boot loader, an error is returned. func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, iblImage secboot_efi.Image) (result *secureBootPolicyResult, err error) { if iblImage == nil { return nil, errors.New("must supply the initial boot loader image") @@ -254,7 +507,7 @@ NextEvent: sigDb, err := checkSecureBootVariableData(data) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid event data for EV_EFI_VARIABLE_DRIVER_CONFIG event: %w", err) } if data.UnicodeName == "db" { // Capture the db from the log for future use. @@ -291,7 +544,57 @@ NextEvent: // Anything that isn't EV_EFI_VARIABLE_DRIVER_CONFIG ends up here. return nil, fmt.Errorf("unexpected %v event %q whilst measuring config", ev.EventType, ev.Data) } - case tcglogPhasePreOSAfterMeasureSecureBootConfig, tcglogPhaseOSPresent: + case tcglogPhasePreOSAfterMeasureSecureBootConfig: + if len(configs) > 0 { + // We've transitioned to a phase where components can be loaded and verified but we haven't + // measured all of the secure boot variables. We'll fail to generate a valid policy with + // WithSecureBootPolicyProfile() in this case. + return nil, errors.New("EV_EFI_VARIABLE_DRIVER_CONFIG events for some secure boot variables missing from log") + } + + if ev.PCRIndex != internal_efi.SecureBootPolicyPCR { + // Not PCR7 + continue NextEvent + } + + switch ev.EventType { + case tcglog.EventTypeEFIVariableAuthority: + eslType, esdData, err := handleVariableAuthorityEvent(pcrAlg, db, measuredSignatures, ev, false) + if err != nil { + return nil, fmt.Errorf("cannot handle EV_EFI_VARIABLE_AUTHORITY event in pre-OS phase: %w", err) + } + + measuredSignatures = append(measuredSignatures, ev.Digests[pcrAlg]) + + ok, err := checkSignatureDataStrength(eslType, esdData) + if err != nil { + return nil, fmt.Errorf("cannot check strength of EFI_SIGNATURE_DATA associated with EV_EFI_VARIABLE_AUTHORITY event in pre-OS phase: %w", err) + } + if !ok { + // XXX: unfortunately in the case where an image is signed, the verification event only includes the CA + // certificate - it's not possible from this to determine the actual signing certificate, it's signature + // algorithm, and the algorithm used for signing the binary. In this case, the check is on the CA's + // public key, which still has some value. + result.Flags |= secureBootIncludesWeakAlg + } + if eslType == efi.CertX509Guid { + cert, err := x509.ParseCertificate(esdData) + if err != nil { + return nil, fmt.Errorf("cannot decode X.509 certificate associated with EV_EFI_VARIABLE_AUTHORITY event in pre-OS phase: %w", err) + } + result.UsedAuthorities = append(result.UsedAuthorities, cert) + } else { + // Hopefully there shouldn't be any components being authenticated by a digest. We don't support this for + // OS components but this could be allowed for pre-OS, but it would make PCR7 incredibly fragile. + result.Flags |= secureBootPreOSVerificationIncludesDigest + } + case tcglog.EventTypeSeparator: + // ok + default: + // Anything that isn't EV_EFI_VARIABLE_AUTHORITY ends up here. + return nil, fmt.Errorf("unexpected %v event %q whilst measuring verification", ev.EventType, ev.Data) + } + case tcglogPhaseOSPresent: if len(configs) > 0 { // We've transitioned to a phase where components can be loaded and verified but we haven't // measured all of the secure boot variables. We'll fail to generate a valid policy with @@ -301,7 +604,6 @@ NextEvent: if ev.PCRIndex == internal_efi.BootManagerCodePCR && ev.EventType == tcglog.EventTypeEFIBootServicesApplication && - phase == tcglogPhaseOSPresent && !seenIBLLoadEvent { // This is an EV_EFI_BOOT_SERVICES_APPLICATION event during OS-present, // and we haven't seen the event for the IBL yet. We stop once we see this @@ -331,54 +633,20 @@ NextEvent: // This is the IBL for the OS. Obtain signatures from binary seenIBLLoadEvent = true - sigs, err := func() ([]*efi.WinCertificateAuthenticode, error) { - r, err := iblImage.Open() - if err != nil { - return nil, fmt.Errorf("cannot open image: %w", err) - } - defer r.Close() - - pefile, err := peNewFile(r) - if err != nil { - return nil, fmt.Errorf("cannot decode image: %w", err) - } - - return internal_efiSecureBootSignaturesFromPEFile(pefile, r) - }() - if err != nil { - return nil, fmt.Errorf("cannot obtain secure boot signatures from image %s: %w", iblImage, err) - } - - // Make sure that one of the CA's used for verification so far - // is a trust anchor for one of the signatures on the image. - var foundSig *efi.WinCertificateAuthenticode - for _, cert := range result.UsedAuthorities { - for _, sig := range sigs { - if sig.CertLikelyTrustAnchor(cert) { - foundSig = sig - break - } - } - if foundSig != nil { - break - } - } - if foundSig == nil { + signer, err := extractSignerWithTrustAnchorFromImage(result.UsedAuthorities, iblImage) + switch { + case err == errNoSignerWithTrustAnchor: return nil, errors.New("OS initial boot loader was not verified by any X.509 certificate measured by any EV_EFI_VARIABLE_AUTHORITY event") + case err != nil: + return nil, fmt.Errorf("cannot determine if OS initial boot loader was verified by any X.509 certificate measured by any EV_EFI_VARIABLE_AUTHORITY event: %w", err) } - signer := foundSig.GetSigner() - switch signer.PublicKeyAlgorithm { - case x509.RSA: - pubKey, ok := signer.PublicKey.(*rsa.PublicKey) - if !ok { - return nil, errors.New("signer certificate for OS initial boot loader contains unsupported public key type") - } - if pubKey.N.BitLen() <= 1024 { - result.Flags |= secureBootIncludesWeakAlg - } - default: - return nil, errors.New("signer certificate for OS initial boot loader contains unsupported public key algorithm") + ok, err := checkX509CertificatePublicKeyStrength(signer) + if err != nil { + return nil, fmt.Errorf("cannot determine public key strength of initial OS boot loader signer: %w", err) + } + if !ok { + result.Flags |= secureBootIncludesWeakAlg } // This is the launch of the IBL. At this point, events are under control of the @@ -394,129 +662,36 @@ NextEvent: switch ev.EventType { case tcglog.EventTypeEFIVariableAuthority: - // Decode the verification event - data, ok := ev.Data.(*tcglog.EFIVariableData) - if !ok { - // if decoding failed, the resulting data is guaranteed to implement error. - return nil, fmt.Errorf("EV_EFI_VARIABLE_AUTHORITY event has wrong data format: %w", ev.Data.(error)) - } - - // As we're only checking events up to the launch of the IBL, we don't expect - // to see anything other than verification events from db here. - if data.VariableName != efi.ImageSecurityDatabaseGuid || data.UnicodeName != "db" { - return nil, fmt.Errorf("EV_EFI_VARIABLE_AUTHORITY event is not from db (got %s-%v)", data.UnicodeName, data.VariableName) - } - - if phase == tcglogPhaseOSPresent { - // Compute the expected digest from the event data in the log and make - // sure it's consistent with the measured digest. We only do this for - // OS-present events because these are the ones we compute. Pre-OS - // events are just copied from the log. - seenOSPresentVerification = true - expectedDigest := tcglog.ComputeEFIVariableDataDigest(pcrAlg.GetHash(), data.UnicodeName, data.VariableName, data.VariableData) - if !bytes.Equal(ev.Digests[pcrAlg], expectedDigest) { - return nil, fmt.Errorf("event data inconsistent with %v event digest for EV_EFI_VARIABLE_AUTHORITY event (log digest:%#x, expected digest:%#x)", pcrAlg, ev.Digests[pcrAlg], expectedDigest) - } - } - - // Make sure that this signature hasn't already been measured. Duplicate signatures measured - // by the firmware may result in incorrectly computed PCR policies. - // Unfortunately, this test isn't 100% reliable as we stop processing events after the launch - // of the IBL (usually shim). Once the IBL has launched, we can't tell whether subsequent events - // were generated by the firmware because an OS component made use of LoadImage (where we would - // want to make sure it isn't measured again) or whether subsequent events are measured via some - // other mechanism by an OS component, such as the shim verification (which we wouldn't want to - // check, because we're only testing firmware compatbility here). I can't think of a way to make - // this 100% reliable other than by ensuring OS components never measure events with "db" and - // IMAGE_SECURITY_DATABASE_GUID in their event data, as a way of being able to distinguish - // firmware generated events from OS component generated events. It's a legitimate scenario for - // both the firmware and shim to both measure the same signature they used for verification from db - // because they both maintain their own de-duplication lists. - // - // If this test fails, the firmware is definitely broken. If this test doesn't fail, the opposite is - // not true - it's not a definitive guarantee that the firmware isn't broken, unfortunately. - for _, measured := range measuredSignatures { - if bytes.Equal(measured, ev.Digests[pcrAlg]) { - return nil, fmt.Errorf("EV_EFI_VARIABLE_AUTHORITY digest %#x has been measured by the firmware more than once", ev.Digests[pcrAlg]) - } - } - measuredSignatures = append(measuredSignatures, ev.Digests[pcrAlg]) - - // Try to discover the type of authentication. The measured EFI_SIGNATURE_DATA doesn't - // contain this. - - // First of all, construct a signature data entry from the raw event data. - esd := new(efi.SignatureData) - r := bytes.NewReader(data.VariableData) - - // THE EFI_SIGNATURE_DATA entry starts with the owner GUID - sigOwner, err := efi.ReadGUID(r) + // Make sure that the expected digest from the event data in the log is consistent + // with the measured digest. We only do this for OS-present events because these are + // the ones we compute. Pre-OS events are just copied from the log, so we don't check + // them. + eslType, esdData, err := handleVariableAuthorityEvent(pcrAlg, db, measuredSignatures, ev, true) if err != nil { - return nil, fmt.Errorf("cannot decode owner GUID from EV_EFI_VARIABLE_AUTHORITY event: %w", err) + return nil, fmt.Errorf("cannot handle EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: %w", err) } - esd.Owner = sigOwner - // The rest of the EFI_SIGNATURE_DATA entry is the data - sigData, err := io.ReadAll(r) + measuredSignatures = append(measuredSignatures, ev.Digests[pcrAlg]) + seenOSPresentVerification = true + ok, err := checkSignatureDataStrength(eslType, esdData) if err != nil { - return nil, fmt.Errorf("cannot read data from EV_EFI_VARIABLE_AUTHORITY event: %w", err) + return nil, fmt.Errorf("cannot check strength of EFI_SIGNATURE_DATA associated with EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: %w", err) } - esd.Data = sigData - - // We have a fully constructed EFI_SIGNATURE_DATA. Now iterate over db to see if this - // EFI_SIGNATURE_DATA belongs to any EFI_SIGNATURE_LIST, in order to grab its type. - var matchedEsl *efi.SignatureList - for _, list := range db { - for _, sig := range list.Signatures { - if sig.Equal(esd) { - matchedEsl = list - break - } - } - if matchedEsl != nil { - break - } + if !ok { + // XXX: unfortunately in the case where an image is signed, the verification event only includes the CA + // certificate - it's not possible from this to determine the actual signing certificate, it's signature + // algorithm, and the algorithm used for signing the binary. In this case, the check is on the CA's + // public key, which still has some value. + result.Flags |= secureBootIncludesWeakAlg } - if matchedEsl == nil { - return nil, fmt.Errorf("encountered db EV_EFI_VARIABLE_AUTHORITY event with data that doesn't match to any db EFI_SIGNATURE_LIST") + if eslType != efi.CertX509Guid { + return nil, errors.New("encountered EV_EFI_VARIABLE_AUTHORITY event without X.509 certificate during OS present, which is not supported") } - - switch matchedEsl.Type { - case efi.CertX509Guid: - cert, err := x509.ParseCertificate(esd.Data) - if err != nil { - return nil, fmt.Errorf("cannot decode X.509 certificate from db EV_EFI_VARIABLE_AUTHORITY event: %w", err) - } - result.UsedAuthorities = append(result.UsedAuthorities, cert) - - switch cert.PublicKeyAlgorithm { - case x509.RSA: - pubKey, ok := cert.PublicKey.(*rsa.PublicKey) - if !ok { - return nil, errors.New("db EV_EFI_VARIABLE_AUTHORITY event includes X.509 certificate with unsupported public key type") - } - if pubKey.N.BitLen() <= 1024 { - result.Flags |= secureBootIncludesWeakAlg - } - default: - return nil, errors.New("db EV_EFI_VARIABLE_AUTHORITY event includes X.509 certificate with unsupported public key algorithm") - } - // XXX: unfortunately the verification event only includes the CA certificate - it's not possible from this to - // determine the actual signing certificate, it's signature algorithm, and the algorithm used for signing the - // binary. - case efi.CertSHA1Guid: - // Hopefully there shouldn't be any components being authenticated by a digest. We don't support this for - // OS components so this might be relevant for pre-OS, but it would make PCR7 incredibly fragile. - result.Flags |= secureBootIncludesWeakAlg - fallthrough - case efi.CertSHA224Guid, efi.CertSHA256Guid, efi.CertSHA384Guid, efi.CertSHA512Guid: - if phase == tcglogPhaseOSPresent { - return nil, errors.New("encountered EV_EFI_VARIABLE_AUTHORITY event without X.509 certificate during OS present, which is not supported") - } - result.Flags |= secureBootPreOSVerificationIncludesDigest - default: - return nil, fmt.Errorf("unrecognized EFI_SIGNATURE_DATA type for EV_EFI_VARIABLE_AUTHORITY event: %v", matchedEsl.Type) + cert, err := x509.ParseCertificate(esdData) + if err != nil { + return nil, fmt.Errorf("cannot decode X.509 certificate associated with EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: %w", err) } + result.UsedAuthorities = append(result.UsedAuthorities, cert) case tcglog.EventTypeSeparator: // ok default: diff --git a/efi/preinstall/check_pcr7_test.go b/efi/preinstall/check_pcr7_test.go index 16315446..9b62941b 100644 --- a/efi/preinstall/check_pcr7_test.go +++ b/efi/preinstall/check_pcr7_test.go @@ -666,7 +666,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, ErrorMatches, `SecureBoot variable is not consistent with the corresponding EV_EFI_VARIABLE_DRIVER_CONFIG event value in the TCG log`) + c.Check(err, ErrorMatches, `invalid event data for EV_EFI_VARIABLE_DRIVER_CONFIG event: SecureBoot value is not consistent with the current EFI variable value`) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadUEFIDebuggerPresent(c *C) { @@ -837,7 +837,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_AUTHORITY event is not from db \(got db-8be4df61-93ca-11d2-aa0d-00e098032b8c\)`) + c.Check(err, ErrorMatches, `cannot handle EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: event is not from db \(got db-8be4df61-93ca-11d2-aa0d-00e098032b8c\)`) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadFirstOSPresentVerificationWrongDigest(c *C) { @@ -872,7 +872,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, ErrorMatches, `event data inconsistent with TPM_ALG_SHA256 event digest for EV_EFI_VARIABLE_AUTHORITY event \(log digest:0x8c8d89cdf0f2de4a1e97d436d7f6a19c49ab55d33bdb81c27470d4140b3de220, expected digest:0x4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9\)`) + c.Check(err, ErrorMatches, `cannot handle EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: event data inconsistent with TPM_ALG_SHA256 event digest \(log digest:0x8c8d89cdf0f2de4a1e97d436d7f6a19c49ab55d33bdb81c27470d4140b3de220, expected digest:0x4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9\)`) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadDuplicateVerificationDigests(c *C) { @@ -920,7 +920,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_AUTHORITY digest 0x4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9 has been measured by the firmware more than once`) + c.Check(err, ErrorMatches, `cannot handle EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: digest 0x4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9 has been measured by the firmware already`) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadInvalidIBLLoadEventData(c *C) { @@ -990,7 +990,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, ErrorMatches, `EV_EFI_VARIABLE_AUTHORITY event has wrong data format: some error`) + c.Check(err, ErrorMatches, `cannot handle EV_EFI_VARIABLE_AUTHORITY event in OS-present phase: event has wong data format: some error`) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadDMAProtectionDisabled(c *C) {