From 0c76820087dd62e5e263d832b129cf26daa8f9cb Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Fri, 4 Oct 2024 14:36:36 +0100 Subject: [PATCH 1/6] preinstall: Add CheckResult and WithAutoTCGPCRProfile APIs CheckResult is returned from RunChecks on successful completion. It is JSON serializable and intended to be supplied later on to WithAutoTCGPCRProfile along with some user customization flags (defined by PCRProfileOptionsFlags) in order to generate an option that can be supplied to secboot_efi.AddPCRProfile. Note that some user options don't work yet because of missing PCR support, although these limitations will be addressed later. Even so, some options may still fail depending on the CheckResult flags. --- efi/preinstall/check_pcr7.go | 10 +- efi/preinstall/check_pcr7_test.go | 37 +- efi/preinstall/check_tcglog.go | 13 +- efi/preinstall/check_tcglog_test.go | 34 +- efi/preinstall/errors.go | 143 ++++- efi/preinstall/export_test.go | 14 + efi/preinstall/pkix.go | 168 ++++++ efi/preinstall/preinstall_test.go | 12 +- efi/preinstall/profile.go | 339 +++++++++++ efi/preinstall/profile_test.go | 527 +++++++++++++++++ efi/preinstall/result.go | 550 ++++++++++++++++++ efi/preinstall/result_test.go | 320 ++++++++++ .../testdata/MicrosoftUefiCA2023.crt | 33 ++ go.mod | 2 +- go.sum | 4 +- internal/efi/known_secureboot_cas.go | 39 ++ 16 files changed, 2165 insertions(+), 80 deletions(-) create mode 100644 efi/preinstall/pkix.go create mode 100644 efi/preinstall/profile.go create mode 100644 efi/preinstall/profile_test.go create mode 100644 efi/preinstall/result.go create mode 100644 efi/preinstall/result_test.go create mode 100644 efi/preinstall/testdata/MicrosoftUefiCA2023.crt diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go index 5d22daad..f8dd0c5a 100644 --- a/efi/preinstall/check_pcr7.go +++ b/efi/preinstall/check_pcr7.go @@ -155,7 +155,7 @@ var errNoSignerWithTrustAnchor = errors.New("image has no signer associated with // 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) { +func extractSignerWithTrustAnchorFromImage(authorities []*X509CertificateID, image secboot_efi.Image) (*x509.Certificate, error) { r, err := image.Open() if err != nil { return nil, fmt.Errorf("cannot open image: %w", err) @@ -177,7 +177,7 @@ func extractSignerWithTrustAnchorFromImage(authorities []*x509.Certificate, imag var foundSig *efi.WinCertificateAuthenticode for _, cert := range authorities { for _, sig := range sigs { - if sig.CertLikelyTrustAnchor(cert) { + if sig.CertWithIDLikelyTrustAnchor(cert) { foundSig = sig break } @@ -295,7 +295,7 @@ const ( // secureBootPolicyResult is the result of a successful call to checkSecureBootPolicyMeasurementsAndObtainAuthorities. type secureBootPolicyResult struct { - UsedAuthorities []*x509.Certificate // CA's used to authenticate boot components. + UsedAuthorities []*X509CertificateID // CA's used to authenticate boot components. Flags secureBootPolicyResultFlags } @@ -582,7 +582,7 @@ NextEvent: 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) + result.UsedAuthorities = append(result.UsedAuthorities, newX509CertificateID(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. @@ -691,7 +691,7 @@ NextEvent: 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) + result.UsedAuthorities = append(result.UsedAuthorities, newX509CertificateID(cert)) case tcglog.EventTypeSeparator: // ok default: diff --git a/efi/preinstall/check_pcr7_test.go b/efi/preinstall/check_pcr7_test.go index 9b62941b..1a72a79a 100644 --- a/efi/preinstall/check_pcr7_test.go +++ b/efi/preinstall/check_pcr7_test.go @@ -22,7 +22,6 @@ package preinstall_test import ( "context" "crypto" - "crypto/x509" "errors" "io" @@ -58,7 +57,7 @@ type testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams struct { iblImage secboot_efi.Image expectedFlags SecureBootPolicyResultFlags - expectedUsedAuthorities []*x509.Certificate + expectedUsedAuthorities []*X509CertificateID } func (s *pcr7Suite) testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c *C, params *testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams) error { @@ -93,7 +92,7 @@ func (s *pcr7Suite) testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c 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) + c.Check(authority, DeepEquals, params.expectedUsedAuthorities[i]) } return nil } @@ -118,8 +117,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPolicyResultFlags(0), - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -145,8 +144,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPolicyResultFlags(0), - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -175,8 +174,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPolicyResultFlags(0), - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -206,8 +205,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPreOSVerificationIncludesDigest, - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -237,8 +236,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootIncludesWeakAlg | SecureBootPreOSVerificationIncludesDigest, - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -267,8 +266,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPolicyResultFlags(0), - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -297,8 +296,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPolicyResultFlags(0), - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) @@ -322,8 +321,8 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoo }, }, expectedFlags: SecureBootPolicyResultFlags(0), - expectedUsedAuthorities: []*x509.Certificate{ - testutil.ParseCertificate(c, msUefiCACert), + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), }, }) c.Check(err, IsNil) diff --git a/efi/preinstall/check_tcglog.go b/efi/preinstall/check_tcglog.go index 70c8b1b6..0fc67bad 100644 --- a/efi/preinstall/check_tcglog.go +++ b/efi/preinstall/check_tcglog.go @@ -84,11 +84,14 @@ func (r *pcrResults) Err() error { } if !r.extended() { // Return an error if the PCR hasn't been extended. + // This generally shouldn't happen because there should at + // least be a EV_SEPARATOR event, and if there isn't one, we + // trigger errors elsewhere related to the structure of the log. return errors.New("PCR has not been extended by platform firmware") } if !bytes.Equal(r.pcrValue, r.logValue) { // The PCR value is inconsistent with the log value. - return fmt.Errorf("PCR value mismatch (actual from TPM %#x, reconstructed from log %#x)", r.pcrValue, r.logValue) + return &PCRValueMismatchError{PCRValue: r.pcrValue, LogValue: r.logValue} } return nil } @@ -257,8 +260,7 @@ func checkFirmwareLogAgainstTPMForAlg(tpm *tpm2.TPMContext, log *tcglog.Log, alg break } if !supported { - // The log doesn't contain the specified algorithm - return nil, errors.New("digest algorithm not present in log") + return nil, ErrPCRBankMissingFromLog } // Create the result tracker for PCRs 0-7 @@ -586,7 +588,10 @@ func checkFirmwareLogAndChoosePCRBank(tpm *tpm2.TPMContext, log *tcglog.Log, man // likely to get SHA-256 here - it's only in very recent devices that we have TPMs with // SHA-384 support and corresponding firmware integration. // We try to keep all errors enountered during selection here. - mainErr := new(NoSuitablePCRAlgorithmError) + mainErr := &NoSuitablePCRAlgorithmError{ + BankErrs: make(map[tpm2.HashAlgorithmId]error), + PCRErrs: make(map[tpm2.HashAlgorithmId]map[tpm2.Handle]error), + } var chosenResults *pcrBankResults for _, alg := range supportedAlgs { if chosenResults != nil { diff --git a/efi/preinstall/check_tcglog_test.go b/efi/preinstall/check_tcglog_test.go index 4878012f..50a4fc50 100644 --- a/efi/preinstall/check_tcglog_test.go +++ b/efi/preinstall/check_tcglog_test.go @@ -360,6 +360,7 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankOldFirmware(c *C) { expectedAlg: tpm2.HashAlgorithmSHA256, }) } + func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankUnexpectedStartupLocality(c *C) { // Test with a StartupLocality event in PCR1 s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) @@ -395,8 +396,8 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankUnexpectedStartupLocal internal_efi.PlatformFirmwarePCR, }) c.Check(err, ErrorMatches, `no suitable PCR algorithm available: -- TPM_ALG_SHA512: digest algorithm not present in log. -- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA512: the PCR bank is missing from the TCG log. +- TPM_ALG_SHA384: the PCR bank is missing from the TCG log. - TPM_ALG_SHA256\(PCR0\): PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\). - TPM_ALG_SHA256\(PCR1\): unexpected StartupLocality event \(should be in PCR0\). `) @@ -404,13 +405,16 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankUnexpectedStartupLocal c.Check(errors.As(err, &e), testutil.IsTrue) // Test that we can access individual errors. - c.Check(e.UnwrapBankError(tpm2.HashAlgorithmSHA512), ErrorMatches, `digest algorithm not present in log`) - c.Check(e.UnwrapBankError(tpm2.HashAlgorithmSHA384), ErrorMatches, `digest algorithm not present in log`) - c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA384, internal_efi.PlatformFirmwarePCR), IsNil) - c.Check(e.UnwrapBankError(tpm2.HashAlgorithmSHA256), IsNil) - c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA256, internal_efi.PlatformFirmwarePCR), ErrorMatches, `PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\)`) - c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA256, internal_efi.PlatformConfigPCR), ErrorMatches, `unexpected StartupLocality event \(should be in PCR0\)`) - c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA256, internal_efi.DriversAndAppsPCR), IsNil) + c.Check(e.BankErrs[tpm2.HashAlgorithmSHA512], Equals, ErrPCRBankMissingFromLog) + c.Check(e.BankErrs[tpm2.HashAlgorithmSHA384], Equals, ErrPCRBankMissingFromLog) + c.Check(e.PCRErrs[tpm2.HashAlgorithmSHA384][internal_efi.PlatformFirmwarePCR], IsNil) + c.Check(e.BankErrs[tpm2.HashAlgorithmSHA256], IsNil) + + var mismatchErr *PCRValueMismatchError + c.Check(e.PCRErrs[tpm2.HashAlgorithmSHA256][internal_efi.PlatformFirmwarePCR], ErrorMatches, `PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\)`) + c.Check(errors.As(e.PCRErrs[tpm2.HashAlgorithmSHA256][internal_efi.PlatformFirmwarePCR], &mismatchErr), testutil.IsTrue) + c.Check(e.PCRErrs[tpm2.HashAlgorithmSHA256][internal_efi.PlatformConfigPCR], ErrorMatches, `unexpected StartupLocality event \(should be in PCR0\)`) + c.Check(e.PCRErrs[tpm2.HashAlgorithmSHA256][internal_efi.DriversAndAppsPCR], IsNil) } func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankOutOfPlaceStartupLocality(c *C) { @@ -471,8 +475,8 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankOutOfPlaceStartupLocal internal_efi.PlatformFirmwarePCR, }) c.Check(err, ErrorMatches, `no suitable PCR algorithm available: -- TPM_ALG_SHA512: digest algorithm not present in log. -- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA512: the PCR bank is missing from the TCG log. +- TPM_ALG_SHA384: the PCR bank is missing from the TCG log. - TPM_ALG_SHA256\(PCR0\): unexpected StartupLocality event after measurements already made. `) var e *NoSuitablePCRAlgorithmError @@ -514,8 +518,8 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankInvalidStartupLocality internal_efi.PlatformFirmwarePCR, }) c.Check(err, ErrorMatches, `no suitable PCR algorithm available: -- TPM_ALG_SHA512: digest algorithm not present in log. -- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA512: the PCR bank is missing from the TCG log. +- TPM_ALG_SHA384: the PCR bank is missing from the TCG log. - TPM_ALG_SHA256\(PCR0\): invalid StartupLocality value 2 - TPM2_Startup is only permitted from locality 0 or 3, or PCR0 can be initialized from locality 4 by a H-CRTM event before TPM2_Startup is called. `) var e *NoSuitablePCRAlgorithmError @@ -540,8 +544,8 @@ func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPCRMismatchMandatory(c internal_efi.PlatformFirmwarePCR, }) c.Check(err, ErrorMatches, `no suitable PCR algorithm available: -- TPM_ALG_SHA512: digest algorithm not present in log. -- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA512: the PCR bank is missing from the TCG log. +- TPM_ALG_SHA384: the PCR bank is missing from the TCG log. - TPM_ALG_SHA256\(PCR0\): PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\). `) var e *NoSuitablePCRAlgorithmError diff --git a/efi/preinstall/errors.go b/efi/preinstall/errors.go index b61b2537..dbe47eec 100644 --- a/efi/preinstall/errors.go +++ b/efi/preinstall/errors.go @@ -20,14 +20,57 @@ package preinstall import ( + "bufio" "bytes" "errors" "fmt" + "io" "github.com/canonical/go-tpm2" internal_efi "github.com/snapcore/secboot/internal/efi" ) +// indentLines is a helper for managing indenting in nested multi-line +// errors. +func indentLines(n int, str string) string { + r := bytes.NewReader([]byte(str)) + w := new(bytes.Buffer) + br := bufio.NewReader(r) + for { + line, err := br.ReadString('\n') + fmt.Fprintf(w, "%*s%s", n, "", line) + if err == io.EOF { + break + } + if err != nil { + fmt.Fprintf(w, "%*serror occurred whilst indenting: %v", n, "", err) + break + } + } + return w.String() +} + +// RunChecksErrors may be returned unwrapped from [RunChecks] containing a collection +// of errors found during the process of running various tests on the platform. +// It provides a mechanism to access each individual error. This is used as an alternative +// to aborting early, in order for the caller to gather as much information as possible. +type RunChecksErrors struct { + Errs []error // All of the errors collected during the execution of RunChecks. +} + +func (e *RunChecksErrors) Error() string { + w := new(bytes.Buffer) + fmt.Fprintf(w, "one or more errors detected:\n") + for _, err := range e.Errs { + fmt.Fprintf(w, "%s\n", indentLines(2, "- "+err.Error())) + } + return w.String() +} + +func (e *RunChecksErrors) addErr(err error) { + e.Errs = append(e.Errs, err) +} + // Errors related to checking platform firmware protections. // NoHardwareRootOfTrustError is returned wrapped from [RunChecks] if the platform @@ -108,8 +151,6 @@ var ( // supported by snapd. Snapd needs the use of both of these hierarchies, so if we // want to support something other than snapd taking ownership of these in the future, // this will need coordination with snapd. - // Unless the authorization values are known, clearing this will most likely require - // the TPM to be cleared. ErrUnsupportedTPMOwnership = errors.New("either the TPM's storage or endorsement hierarchy is owned and this isn't currently supported") // ErrTPMInsufficientNVCounters is returned wrapped from RunChecks if there are @@ -134,13 +175,32 @@ var ( // Errors related to general TCG log checks and PCR bank selection. +var ( + // ErrPCRBankMissingFromLog may be returned wrapped by NoSuitablePCRAlgorithmError + // in the event where a PCR bank does not exist in the TCG log. It may be obtained from + // the BankErrs field. + ErrPCRBankMissingFromLog = errors.New("the PCR bank is missing from the TCG log") +) + +// PCRValueMismatchError may be returned wrapped by NoSuitablePCRAlgorithmError for a specific +// PCR in the event where there is a mismatch between the actual PCR value and the value reconstructed +// from the TCG log. It may be obtained from the PCRErrs field. +type PCRValueMismatchError struct { + PCRValue tpm2.Digest // The PCR value obtained from the TPM. + LogValue tpm2.Digest // The expected value reconstructed from the TCG log. +} + +func (e *PCRValueMismatchError) Error() string { + return fmt.Sprintf("PCR value mismatch (actual from TPM %#x, reconstructed from log %#x)", e.PCRValue, e.LogValue) +} + // NoSuitablePCRAlgorithmError is returned wrapped from [RunChecks] if there is no suitable PCR bank // where the log matches the TPM values when reconstructed. As multiple errors can occur during // testing (multiple banks and multiple PCRs), this error tries to keep as much information as // possible type NoSuitablePCRAlgorithmError struct { - bankErrs map[tpm2.HashAlgorithmId]error // bankErrs apply to an entire PCR bank - pcrErrs map[tpm2.HashAlgorithmId]map[tpm2.Handle]error // pcrErrs apply to a single PCR in a single bank + BankErrs map[tpm2.HashAlgorithmId]error // BankErrs apply to an entire PCR bank + PCRErrs map[tpm2.HashAlgorithmId]map[tpm2.Handle]error // PCRErrs apply to a single PCR in a single bank } func (e *NoSuitablePCRAlgorithmError) Error() string { @@ -152,13 +212,13 @@ func (e *NoSuitablePCRAlgorithmError) Error() string { // go maps don't guarantee when iterating over keys). for _, alg := range supportedAlgs { // Print error for this PCR bank first, if there is one. - if err, isErr := e.bankErrs[alg]; isErr { + if err, isErr := e.BankErrs[alg]; isErr { // We have a general error for this PCR bank fmt.Fprintf(w, "- %v: %v.\n", alg, err) } // Then print errors associated with individual PCRs in this bank. - pcrErrs, hasPcrErrs := e.pcrErrs[alg] + pcrErrs, hasPcrErrs := e.PCRErrs[alg] if !hasPcrErrs { // We have no individual PCR errors for this bank continue @@ -173,36 +233,14 @@ func (e *NoSuitablePCRAlgorithmError) Error() string { return w.String() } -// UnwrapBankError returns the error associated with the specified PCR bank if one -// occurred, or nil if none occurred. -func (e *NoSuitablePCRAlgorithmError) UnwrapBankError(alg tpm2.HashAlgorithmId) error { - return e.bankErrs[alg] -} - -// UnwrapPCRError returns the error associated with the specified PCR in the specified -// bank if one occurred, or nil if none occurred. -func (e *NoSuitablePCRAlgorithmError) UnwrapPCRError(alg tpm2.HashAlgorithmId, pcr tpm2.Handle) error { - pcrErrs, exists := e.pcrErrs[alg] - if !exists { - return nil - } - return pcrErrs[pcr] -} - // setBankErr sets an error for an entire PCR bank func (e *NoSuitablePCRAlgorithmError) setBankErr(alg tpm2.HashAlgorithmId, err error) { - if e.bankErrs == nil { - e.bankErrs = make(map[tpm2.HashAlgorithmId]error) - } - e.bankErrs[alg] = err + e.BankErrs[alg] = err } // setPcrErrs sets errors for individual PCRs associated with a bank func (e *NoSuitablePCRAlgorithmError) setPcrErrs(results *pcrBankResults) { - if e.pcrErrs == nil { - e.pcrErrs = make(map[tpm2.HashAlgorithmId]map[tpm2.Handle]error) - } - e.pcrErrs[results.Alg] = results.pcrErrs() + e.PCRErrs[results.Alg] = results.pcrErrs() } // Errors related to secure boot policy PCR checks. @@ -216,3 +254,48 @@ var ( // 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") ) + +// UnsupportedReqiredPCRsError is returned from methods of [PCRProfileAutoEnablePCRsOption] +// when a valid PCR configuration cannot be created based on the supplied [PCRProfileOptionsFlags] +// and [CheckResult]. +type UnsupportedRequiredPCRsError struct { + PCRs tpm2.HandleList +} + +func newUnsupportedRequiredPCRsError(required tpm2.HandleList, flags CheckResultFlags) *UnsupportedRequiredPCRsError { + var pcrs tpm2.HandleList + for _, pcr := range required { + var flag CheckResultFlags + switch pcr { + case 0: + flag = NoPlatformFirmwareProfileSupport + case 1: + flag = NoPlatformConfigProfileSupport + case 2: + flag = NoDriversAndAppsProfileSupport + case 3: + flag = NoDriversAndAppsConfigProfileSupport + case 4: + flag = NoBootManagerCodeProfileSupport + case 5: + flag = NoBootManagerConfigProfileSupport + case 7: + flag = NoSecureBootPolicyProfileSupport + } + + if flags&flag > 0 { + pcrs = append(pcrs, pcr) + } + } + + return &UnsupportedRequiredPCRsError{pcrs} +} + +func (e *UnsupportedRequiredPCRsError) Error() string { + switch len(e.PCRs) { + case 1: + return fmt.Sprintf("PCR %v is required, but is unsupported", e.PCRs[0]) + default: + return fmt.Sprintf("PCRs %v are required, but are unsupported", e.PCRs) + } +} diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index ad34d630..4e471a54 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -28,6 +28,9 @@ import ( ) type ( + AuthorityTrust = authorityTrust + AuthorityTrustData = authorityTrustData + AuthorityTrustDataSet = authorityTrustDataSet BootManagerCodeResultFlags = bootManagerCodeResultFlags CheckDriversAndAppsMeasurementsResult = checkDriversAndAppsMeasurementsResult CheckTPM2DeviceFlags = checkTPM2DeviceFlags @@ -39,6 +42,8 @@ type ( ) const ( + AuthorityTrustBootCode = authorityTrustBootCode + AuthorityTrustDrivers = authorityTrustDrivers BootManagerCodeSysprepAppsPresent = bootManagerCodeSysprepAppsPresent BootManagerCodeAbsoluteComputraceRunning = bootManagerCodeAbsoluteComputraceRunning BootManagerCodeNotAllLaunchDigestsVerified = bootManagerCodeNotAllLaunchDigestsVerified @@ -73,6 +78,7 @@ var ( DetectVirtualization = detectVirtualization DetermineCPUVendor = determineCPUVendor IsLaunchedFromLoadOption = isLaunchedFromLoadOption + NewX509CertificateID = newX509CertificateID OpenAndCheckTPM2Device = openAndCheckTPM2Device ReadCurrentBootLoadOptionFromLog = readCurrentBootLoadOptionFromLog ReadIntelHFSTSRegistersFromMEISysfs = readIntelHFSTSRegistersFromMEISysfs @@ -96,6 +102,14 @@ func MockInternalEfiSecureBootSignaturesFromPEFile(fn func(*pe.File, io.ReaderAt } } +func MockKnownCAs(set AuthorityTrustDataSet) (restore func()) { + orig := knownCAs + knownCAs = set + return func() { + knownCAs = orig + } +} + func MockPeNewFile(fn func(io.ReaderAt) (*pe.File, error)) (restore func()) { orig := peNewFile peNewFile = fn diff --git a/efi/preinstall/pkix.go b/efi/preinstall/pkix.go new file mode 100644 index 00000000..aab7389c --- /dev/null +++ b/efi/preinstall/pkix.go @@ -0,0 +1,168 @@ +// -*- 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 ( + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "unicode" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/crypto/cryptobyte" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" +) + +// This file copies some internal functions from go's crypto/x509 so that we +// can turn raw subjects and issuers into printable strings without having to +// save them. There's even a comment in the go repo about making these public +// for use inside crypto/tls. This code has been copied numodified and so +// should handle any certificate that go's crypto/x509 package can. + +// isPrintable reports whether the given b is in the ASN.1 PrintableString set. +// This is a simplified version of encoding/asn1.isPrintable. +func isPrintable(b byte) bool { + return 'a' <= b && b <= 'z' || + 'A' <= b && b <= 'Z' || + '0' <= b && b <= '9' || + '\'' <= b && b <= ')' || + '+' <= b && b <= '/' || + b == ' ' || + b == ':' || + b == '=' || + b == '?' || + // This is technically not allowed in a PrintableString. + // However, x509 certificates with wildcard strings don't + // always use the correct string type so we permit it. + b == '*' || + // This is not technically allowed either. However, not + // only is it relatively common, but there are also a + // handful of CA certificates that contain it. At least + // one of which will not expire until 2027. + b == '&' +} + +func isIA5String(s string) error { + for _, r := range s { + // Per RFC5280 "IA5String is limited to the set of ASCII characters" + if r > unicode.MaxASCII { + return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s) + } + } + + return nil +} + +// parseASN1String parses the ASN.1 string types T61String, PrintableString, +// UTF8String, BMPString, IA5String, and NumericString. This is mostly copied +// from the respective encoding/asn1.parse... methods, rather than just +// increasing the API surface of that package. +func parseASN1String(tag cryptobyte_asn1.Tag, value []byte) (string, error) { + switch tag { + case cryptobyte_asn1.T61String: + return string(value), nil + case cryptobyte_asn1.PrintableString: + for _, b := range value { + if !isPrintable(b) { + return "", errors.New("invalid PrintableString") + } + } + return string(value), nil + case cryptobyte_asn1.UTF8String: + if !utf8.Valid(value) { + return "", errors.New("invalid UTF-8 string") + } + return string(value), nil + case cryptobyte_asn1.Tag(asn1.TagBMPString): + if len(value)%2 != 0 { + return "", errors.New("invalid BMPString") + } + + // Strip terminator if present. + if l := len(value); l >= 2 && value[l-1] == 0 && value[l-2] == 0 { + value = value[:l-2] + } + + s := make([]uint16, 0, len(value)/2) + for len(value) > 0 { + s = append(s, uint16(value[0])<<8+uint16(value[1])) + value = value[2:] + } + + return string(utf16.Decode(s)), nil + case cryptobyte_asn1.IA5String: + s := string(value) + if isIA5String(s) != nil { + return "", errors.New("invalid IA5String") + } + return s, nil + case cryptobyte_asn1.Tag(asn1.TagNumericString): + for _, b := range value { + if !('0' <= b && b <= '9' || b == ' ') { + return "", errors.New("invalid NumericString") + } + } + return string(value), nil + } + return "", fmt.Errorf("unsupported string type: %v", tag) +} + +// parseName parses a DER encoded Name as defined in RFC 5280. +func parseName(raw cryptobyte.String) (*pkix.RDNSequence, error) { + if !raw.ReadASN1(&raw, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("invalid RDNSequence") + } + + var rdnSeq pkix.RDNSequence + for !raw.Empty() { + var rdnSet pkix.RelativeDistinguishedNameSET + var set cryptobyte.String + if !raw.ReadASN1(&set, cryptobyte_asn1.SET) { + return nil, errors.New("invalid RDNSequence") + } + for !set.Empty() { + var atav cryptobyte.String + if !set.ReadASN1(&atav, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("invalid RDNSequence: invalid attribute") + } + var attr pkix.AttributeTypeAndValue + if !atav.ReadASN1ObjectIdentifier(&attr.Type) { + return nil, errors.New("invalid RDNSequence: invalid attribute type") + } + var rawValue cryptobyte.String + var valueTag cryptobyte_asn1.Tag + if !atav.ReadAnyASN1(&rawValue, &valueTag) { + return nil, errors.New("invalid RDNSequence: invalid attribute value") + } + var err error + attr.Value, err = parseASN1String(valueTag, rawValue) + if err != nil { + return nil, fmt.Errorf("invalid RDNSequence: invalid attribute value: %s", err) + } + rdnSet = append(rdnSet, attr) + } + + rdnSeq = append(rdnSeq, rdnSet) + } + + return &rdnSeq, nil +} diff --git a/efi/preinstall/preinstall_test.go b/efi/preinstall/preinstall_test.go index 00ab1a16..4d29f64b 100644 --- a/efi/preinstall/preinstall_test.go +++ b/efi/preinstall/preinstall_test.go @@ -40,22 +40,26 @@ var ( //go:embed testdata/MicrosoftUefiCA.crt msUefiCACertPEM []byte + //go:embed testdata/MicrosoftUefiCA2023.crt + msUefiCACert2023PEM []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 + msUefiCACert []byte + msUefiCACert2023 []byte + shimUbuntuSig4 []byte + snakeoilCert []byte ) func init() { tpm2_testutil.AddCommandLineFlags() msUefiCACert = testutil.MustDecodePEMType("CERTIFICATE", msUefiCACertPEM) + msUefiCACert2023 = testutil.MustDecodePEMType("CERTIFICATE", msUefiCACert2023PEM) shimUbuntuSig4 = testutil.MustDecodePEMType("PKCS7", shimUbuntuSig4PEM) snakeoilCert = testutil.MustDecodePEMType("CERTIFICATE", snakeoilCertPEM) } diff --git a/efi/preinstall/profile.go b/efi/preinstall/profile.go new file mode 100644 index 00000000..451ab45d --- /dev/null +++ b/efi/preinstall/profile.go @@ -0,0 +1,339 @@ +// -*- 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" + "errors" + "fmt" + + "github.com/canonical/go-tpm2" + secboot_efi "github.com/snapcore/secboot/efi" + internal_efi "github.com/snapcore/secboot/internal/efi" +) + +type authorityTrust int + +const ( + authorityTrustBootCode authorityTrust = 1 << iota // authority is trusted to load boot code (we don't need PCR4) + authorityTrustDrivers // authority is trusted to load drivers (we may not need PCR2) +) + +type authorityTrustDataSet []authorityTrustData + +func (s authorityTrustDataSet) determineTrust(certs []*X509CertificateID) authorityTrust { + trust := authorityTrustBootCode | authorityTrustDrivers + for _, cert := range certs { + var certTrust authorityTrust + for _, auth := range s { + if !bytes.Equal(auth.Authority.Subject, cert.RawSubject()) { + continue + } + if !bytes.Equal(auth.Authority.SubjectKeyId, cert.SubjectKeyId()) { + continue + } + if auth.Authority.PublicKeyAlgorithm != cert.PublicKeyAlgorithm() { + continue + } + if !bytes.Equal(auth.Authority.Issuer, cert.RawIssuer()) { + continue + } + if !bytes.Equal(auth.Authority.AuthorityKeyId, cert.AuthorityKeyId()) { + continue + } + if auth.Authority.SignatureAlgorithm != cert.SignatureAlgorithm() { + continue + } + certTrust = auth.Trust + break + } + trust &= certTrust + } + + return trust +} + +func (s authorityTrustDataSet) trustedForBootManager(certs []*X509CertificateID) bool { + return s.determineTrust(certs)&authorityTrustBootCode > 0 +} + +func (s authorityTrustDataSet) trustedForDrivers(certs []*X509CertificateID) bool { + return s.determineTrust(certs)&authorityTrustDrivers > 0 +} + +type authorityTrustData struct { + Authority *internal_efi.SecureBootAuthorityIdentity + Trust authorityTrust +} + +var ( + knownCAs = authorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, 0}, // be conservative here for now, but will we be able to set the authorityTrustDrivers flag for the MS2023 CA? + } +) + +// PCRProfileOptionsFlags provides a way to customize [WithAutoTCGPCRProfile]. +type PCRProfileOptionsFlags uint32 + +const ( + // PCRProfileOptionsDefault is the default PCR configuration. WithAutoTCGPCRProfile + // will select the most appropriate configuration depending on the CheckResult. + PCRProfileOptionsDefault PCRProfileOptionsFlags = 0 + + // PCRProfileOptionMostSecure is the most secure configuration by + // including all relevant TCG defined PCRs supported by the efi package + // (PCRs 0, 1, 2, 3, 4, 5 and 7). + // + // Note that this option will currently not work because the efi package + // does not support PCRs 1, 3 and 5, but will do in the future. + PCRProfileOptionMostSecure PCRProfileOptionsFlags = 1 << iota + + // PCRProfileOptionTrustCAsForBootCode can omit PCR4 if CAs in the authorized + // signature database that were used to authenticate code on the current boot + // are not directly trusted to sign boot code, but a system administrator makes + // an explicit decision to trust these CAs. This might be because it uses custom + // CAs that are unrecognized for trust by this package. + PCRProfileOptionTrustCAsForBootCode + + // PCRProfileOptionTrustCAsForVARSuppliedDrivers can omit PCR2 if the CAs in the + // authorized signature database that were used to authenticate code on the current + // boot are not directly trusted to sign UEFI drivers, but a system administrator + // makes an explicit decision to trust these CAs. This might be because it uses + // custom CAs that are unrecognized for trust by this package. + PCRProfileOptionTrustCAsForVARSuppliedDrivers + + // PCRProfileOptionDistrustVARSuppliedNonHostCode can be used to include PCR2 if a + // system administrator makes an explicit decision to not trust non host code running + // on attached embedded controllers in value-added-retailer components - this is code + // that is not part of the host's trust chain but may still affect trust in the platform. + PCRProfileOptionDistrustVARSuppliedNonHostCode + + // PCRProfileOptionPermitNoSecureBootPolicyProfle can be used to permit a fallback to + // a configuration without the secure boot policy included if the supplied CheckResult + // indicates that PCR7 cannot be used. + PCRProfileOptionPermitNoSecureBootPolicyProfile + + // PCRProfileOptionNoDiscreteTPMResetMitigation can be used to omit PCR0 from the + // profile on platforms that have a discrete TPM and where including PCR0 can provide + // limited mitigation of TPM reset attacks by preventing the PCR values from being + // reconstructed from software. This should only be used if a system administrator makes + // an explicit decision that they don't want the additional PCR fragility caused by this + // mitigation, perhaps because they consider that discrete TPMs still have other + // weaknesses to anyone with physical access to the device without any of their own + // mitigations. See the DiscreteTPMDetected CheckResultFlags flag description for more + // information. + PCRProfileOptionNoDiscreteTPMResetMitigation +) + +// PCRProfileAutoEnablePCRsOption is an option for AddPCRProfile that adds one or more PCRs +// based on a set of tests done at some point in the past. +type PCRProfileAutoEnablePCRsOption interface { + secboot_efi.PCRProfileEnablePCRsOption + + // Options returns a new PCRProfileAutoEnablePCRsOption instance with + // the specified options applied. + Options(opts PCRProfileOptionsFlags) PCRProfileAutoEnablePCRsOption +} + +type pcrProfileAutoSetPcrsOption struct { + secboot_efi.PCRProfileEnablePCRsOption + + result *CheckResult + opts PCRProfileOptionsFlags +} + +// WithAutoTCGPCRProfile returns a profile for the TCG defined PCRs based on the supplied result +// of [RunChecks] and the specified user options. +func WithAutoTCGPCRProfile(r *CheckResult, opts PCRProfileOptionsFlags) PCRProfileAutoEnablePCRsOption { + out := &pcrProfileAutoSetPcrsOption{ + result: r, + opts: opts, + } + out.PCRProfileEnablePCRsOption = out + return out +} + +func (o *pcrProfileAutoSetPcrsOption) options() ([]secboot_efi.PCRProfileEnablePCRsOption, error) { + switch { + case o.opts&PCRProfileOptionMostSecure > 0: + if o.opts != PCRProfileOptionMostSecure { + return nil, errors.New("PCRProfileOptionMostSecure can only be used on its own") + } + const mask = NoPlatformFirmwareProfileSupport | + NoPlatformConfigProfileSupport | + NoDriversAndAppsProfileSupport | + NoDriversAndAppsConfigProfileSupport | + NoBootManagerCodeProfileSupport | + NoBootManagerConfigProfileSupport | + NoSecureBootPolicyProfileSupport + if o.result.Flags&mask > 0 { + return nil, fmt.Errorf("PCRProfileOptionMostSecure cannot be used: %w", newUnsupportedRequiredPCRsError(tpm2.HandleList{0, 1, 2, 3, 4, 5, 7}, o.result.Flags)) + } + + // TODO: remove this once the secboot_efi package implements support for the remaining PCRs + return nil, fmt.Errorf("PCRProfileOptionMostSecure cannot be used because it is currently unsupported: %w", + newUnsupportedRequiredPCRsError(tpm2.HandleList{0, 1, 2, 3, 4, 5, 7}, NoPlatformConfigProfileSupport|NoDriversAndAppsConfigProfileSupport|NoBootManagerConfigProfileSupport)) + // return []secboot_efi.PCRProfileEnablePCRsOption{ + // secboot_efi.WithPlatformFirmwareProfile(), + // //secboot_efi.WithPlatformConfigProfile(), // TODO: implement in secboot_efi package + // secboot_efi.WithDriversAndAppsProfile(), + // //secboot_efi.WithDriversAndAppsConfigProfile() // TODO: implement in secboot_efi package + // secboot_efi.WithBootManagerCodeProfile(), + // //secboot_efi.WithBootManagerConfigProfile(), // TODO: implement in secboot_efi package + // efi.WithSecureBootPolicyProfile(), + // }, nil + default: + var opts []secboot_efi.PCRProfileEnablePCRsOption + switch { + case o.result.Flags&NoSecureBootPolicyProfileSupport == 0: + // If PCR7 usage is ok, always include it + opts = append(opts, secboot_efi.WithSecureBootPolicyProfile()) + + if !knownCAs.trustedForBootManager(o.result.UsedSecureBootCAs) && o.opts&PCRProfileOptionTrustCAsForBootCode == 0 { + // We need to include PCR4 if any CAs used for verification are not generally trusted to sign boot applications + // (ie, they may have signed code in the past that can defeat our security model, such as versions of shim that + // don't extend anything to the TPM, breaking the root-of-trust. This is true of the Microsoft UEFI CA 2011, + // and for now, we assume to be true of the 2023 UEFI CA unless Microsoft are more transparent about what is + // signed under this CA). It's also assumed to be true for any unrecognized CAs. + // This can be overridden with PCRProfileOptionsTrustCAsForBootCode. + if o.result.Flags&NoBootManagerCodeProfileSupport > 0 { + return nil, fmt.Errorf("cannot create a valid secure boot configuration: one or more CAs used for secure boot "+ + "verification are not trusted to authenticate boot code and the PCRProfileOptionTrustCAsForBootCode "+ + "option was not supplied: %w", newUnsupportedRequiredPCRsError(tpm2.HandleList{4}, o.result.Flags)) + } + opts = append(opts, secboot_efi.WithBootManagerCodeProfile()) + } + + isPcr2Supported := o.result.Flags&NoDriversAndAppsProfileSupport == 0 + + includePcr2 := o.opts&PCRProfileOptionDistrustVARSuppliedNonHostCode > 0 + if includePcr2 && !isPcr2Supported { + // Include PCR2 if the user explicitly distrusts non-host code running + // in attached embedded controllers. + return nil, fmt.Errorf("PCRProfileOptionDistrustVARSuppliedNonHostCode cannot be used: %w", newUnsupportedRequiredPCRsError(tpm2.HandleList{2}, o.result.Flags)) + } + if !knownCAs.trustedForDrivers(o.result.UsedSecureBootCAs) && o.opts&PCRProfileOptionTrustCAsForVARSuppliedDrivers == 0 { + // We need to include PCR2 if any CAs used for verification are not generally trusted to sign UEFI drivers + // (ie, they may have signed code in the past that can defeat our security model. This is true of the Microsoft + // UEFI CA 2011, and for now, we assume to be true of the 2023 UEFI CA unless Microsoft are more transparent about + // what they sign under this CA). It's also assumed to be true for any unrecognized CAs. + // This can be overridden with PCRProfileOptionsTrustCAsForVARSuppliedDrivers. + includePcr2 = true + if !isPcr2Supported { + return nil, fmt.Errorf("cannot create a valid secure boot configuration: one or more CAs used for secure boot "+ + "verification are not trusted to authenticate value-added-retailer suppled drivers and the "+ + "PCRProfileOptionTrustCAsForVARSuppliedDrivers option was not supplied: %w", + newUnsupportedRequiredPCRsError(tpm2.HandleList{2}, o.result.Flags)) + } + } + if includePcr2 { + opts = append(opts, secboot_efi.WithDriversAndAppsProfile()) + } + case o.opts&PCRProfileOptionPermitNoSecureBootPolicyProfile == 0: + // PCR 7 usage is not ok and the user hasn't opted into permitting configurations without it + return nil, fmt.Errorf("cannot create a valid configuration without secure boot policy and the "+ + "PCRProfileOptionPermitNoSecureBootPolicyProfile option was not supplied: %w", + newUnsupportedRequiredPCRsError(tpm2.HandleList{7}, o.result.Flags)) + default: + // PCR 7 usage is not ok and the user has opted into permitting configutations without it. We must include PCRs + // 1, 2, 3, 4 and 5 - none of these can be omitted. + // - PCR1 is required because we have to depend on platform config rather relying on security-relevant firmware + // setting such as DMA protection changing the value of PCR7. + // - PCR2 is required to include all non-platform value-added-retailer supplied drivers that execute. + // - PCR3 is required for the same reason as PCR1, but for value-added-retailer driver configuration. + // - PCR4 is required for all system preparation applications and boot manager code that execute. + // - PCR5 is required for the same reason as PCR1, but for boot manager configuration. + const mask = NoPlatformConfigProfileSupport | + NoDriversAndAppsProfileSupport | + NoDriversAndAppsConfigProfileSupport | + NoBootManagerCodeProfileSupport | + NoBootManagerConfigProfileSupport + if o.result.Flags&mask > 0 { + return nil, fmt.Errorf("cannot create a valid configuration without secure boot policy: %w", newUnsupportedRequiredPCRsError(tpm2.HandleList{1, 2, 3, 4, 5}, o.result.Flags)) + } + + // TODO: remove this once the secboot_efi package implements support for the remaining PCRs + return nil, fmt.Errorf("cannot create a configuration without secure boot policy because this is currently unsupported: %w", + newUnsupportedRequiredPCRsError(tpm2.HandleList{1, 2, 3, 4, 5}, NoPlatformConfigProfileSupport|NoDriversAndAppsConfigProfileSupport|NoBootManagerConfigProfileSupport)) + // opts = append(opts, + // //secboot_efi.WithPlatformConfigProfile(), // TODO: implement in efi package + // secboot_efi.WithDriversAndAppsProfile(), + // //secboot_efi.WithDriversAndAppsConfigProfile(), // TODO: implement in efi package + // secboot_efi.WithBootManagerCodeProfile(), + // //secboot_efi.WithBootManagerConfigProfile(), // TODO: implement in efi package + // ) + + } + if o.opts&PCRProfileOptionNoDiscreteTPMResetMitigation == 0 { + const mask = DiscreteTPMDetected | StartupLocalityNotProtected + if o.result.Flags&mask == DiscreteTPMDetected { + // Enable reset attack mitigations by including PCR0, because the startup locality + // is protected making it impossible to reconstruct PCR0 from software if the TPM is + // reset indepdendently of the host platform. Note that it is still possible for an + // adversary with physical access to reconstruct PCR0 by manipulating the bus between + // the host CPU and the discrete TPM directly, as this will allow them access to all + // localities. + if o.result.Flags&NoPlatformFirmwareProfileSupport > 0 { + return nil, fmt.Errorf("cannot enable a discrete TPM reset attack mitigation and the "+ + "PCRProfileOptionNoDiscreteTPMResetMitigation was not supplied: %w", newUnsupportedRequiredPCRsError(tpm2.HandleList{0}, o.result.Flags)) + } + opts = append(opts, secboot_efi.WithPlatformFirmwareProfile()) + } + } + return opts, nil + } +} + +// ApplyOptionTo implements [secboot_efi.PCRProfileOption]. +func (o *pcrProfileAutoSetPcrsOption) ApplyOptionTo(visitor internal_efi.PCRProfileOptionVisitor) error { + opts, err := o.options() + if err != nil { + return fmt.Errorf("cannot select an appropriate set of TCG defined PCRs with the current options: %w", err) + } + for i, opt := range opts { + if err := opt.ApplyOptionTo(visitor); err != nil { + return fmt.Errorf("cannot add PCR profile option %d: %w", i, err) + } + } + return nil +} + +func (o *pcrProfileAutoSetPcrsOption) Options(opts PCRProfileOptionsFlags) PCRProfileAutoEnablePCRsOption { + return WithAutoTCGPCRProfile(o.result, opts) +} + +// PCRs implements [secboot_efi.PCRProfileEnablePCRsOption.PCRs]. +func (o *pcrProfileAutoSetPcrsOption) PCRs() (tpm2.HandleList, error) { + opts, err := o.options() + if err != nil { + return nil, fmt.Errorf("cannot select an appropriate set of TCG defined PCRs with the current options: %w", err) + } + + var out tpm2.HandleList + for i, opt := range opts { + pcrs, err := opt.PCRs() + if err != nil { + return nil, fmt.Errorf("cannot add PCRs from profile option %d: %w", i, err) + } + out = append(out, pcrs...) + } + return out, nil +} diff --git a/efi/preinstall/profile_test.go b/efi/preinstall/profile_test.go new file mode 100644 index 00000000..b2479145 --- /dev/null +++ b/efi/preinstall/profile_test.go @@ -0,0 +1,527 @@ +// -*- 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 ( + "errors" + + "github.com/canonical/go-tpm2" + . "github.com/snapcore/secboot/efi/preinstall" + internal_efi "github.com/snapcore/secboot/internal/efi" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +type profileSuite struct{} + +var _ = Suite(&profileSuite{}) + +type mockPcrProfileOptionVisitor struct { + pcrs tpm2.HandleList +} + +func (v *mockPcrProfileOptionVisitor) AddPCRs(pcrs ...tpm2.Handle) { + v.pcrs = append(v.pcrs, pcrs...) +} + +func (*mockPcrProfileOptionVisitor) SetEnvironment(env internal_efi.HostEnvironmentEFI) { + panic("not reached") +} + +func (*mockPcrProfileOptionVisitor) AddInitialVariablesModifier(fn internal_efi.InitialVariablesModifier) { + panic("not reached") +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefault(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultDiscreteTPM(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4, 2, 0}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4, 2, 0}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultDiscreteTPMNoResetMitigation(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected | StartupLocalityNotProtected, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultCATrustedForBootCode(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, AuthorityTrustBootCode}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultCATrustedForDrivers(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, AuthorityTrustDrivers}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultCATrustedForDriversAndBootCode(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, AuthorityTrustBootCode | AuthorityTrustDrivers}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultCATrustedForDriversAndBootCodeDiscreteTPM(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, AuthorityTrustBootCode | AuthorityTrustDrivers}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 0}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 0}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultUnrecognizedCA(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2023, 0}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultNoBootManagerCodeSupport(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid secure boot configuration: one or more CAs used for secure boot verification are not trusted to authenticate boot code and the PCRProfileOptionTrustCAsForBootCode option was not supplied: PCR 0x00000004 is required, but is unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid secure boot configuration: one or more CAs used for secure boot verification are not trusted to authenticate boot code and the PCRProfileOptionTrustCAsForBootCode option was not supplied: PCR 0x00000004 is required, but is unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultNoDriversAndAppsSupport(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid secure boot configuration: one or more CAs used for secure boot verification are not trusted to authenticate value-added-retailer suppled drivers and the PCRProfileOptionTrustCAsForVARSuppliedDrivers option was not supplied: PCR 0x00000002 is required, but is unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid secure boot configuration: one or more CAs used for secure boot verification are not trusted to authenticate value-added-retailer suppled drivers and the PCRProfileOptionTrustCAsForVARSuppliedDrivers option was not supplied: PCR 0x00000002 is required, but is unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultNoSecureBootPolicyProfileSupport(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | NoSecureBootPolicyProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid configuration without secure boot policy and the PCRProfileOptionPermitNoSecureBootPolicyProfile option was not supplied: PCR 0x00000007 is required, but is unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid configuration without secure boot policy and the PCRProfileOptionPermitNoSecureBootPolicyProfile option was not supplied: PCR 0x00000007 is required, but is unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultDiscreteTPMNoPlatformFirmwareProfileSupport(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformFirmwareProfileSupport | NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot enable a discrete TPM reset attack mitigation and the PCRProfileOptionNoDiscreteTPMResetMitigation was not supplied: PCR 0x00000000 is required, but is unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot enable a discrete TPM reset attack mitigation and the PCRProfileOptionNoDiscreteTPMResetMitigation was not supplied: PCR 0x00000000 is required, but is unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileMostSecure(c *C) { + // This is an error for now, but will work in the future when we've added + // support for the missing PCRs. + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | NoSecureBootPolicyProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionMostSecure) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionMostSecure cannot be used: PCRs \[0x00000001 0x00000003 0x00000005 0x00000007\] are required, but are unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionMostSecure cannot be used: PCRs \[0x00000001 0x00000003 0x00000005 0x00000007\] are required, but are unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileMostSecure2(c *C) { + // This is an error for now, but will work in the future when we've added + // support for the missing PCRs. + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionMostSecure) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionMostSecure cannot be used because it is currently unsupported: PCRs \[0x00000001 0x00000003 0x00000005\] are required, but are unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionMostSecure cannot be used because it is currently unsupported: PCRs \[0x00000001 0x00000003 0x00000005\] are required, but are unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultNoSecureBootPolicyProfileSupportOptIn(c *C) { + // This is an error for now, but will work in the future when we've added + // support for the missing PCRs. + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | NoSecureBootPolicyProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionPermitNoSecureBootPolicyProfile) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid configuration without secure boot policy: PCRs \[0x00000001 0x00000003 0x00000005\] are required, but are unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a valid configuration without secure boot policy: PCRs \[0x00000001 0x00000003 0x00000005\] are required, but are unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultNoSecureBootPolicyProfileSupportOptIn2(c *C) { + // This is an error for now, but will work in the future when we've added + // support for the missing PCRs. + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoSecureBootPolicyProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionPermitNoSecureBootPolicyProfile) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a configuration without secure boot policy because this is currently unsupported: PCRs \[0x00000001 0x00000003 0x00000005\] are required, but are unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: cannot create a configuration without secure boot policy because this is currently unsupported: PCRs \[0x00000001 0x00000003 0x00000005\] are required, but are unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileMostSecureWithOtherOptions(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | NoSecureBootPolicyProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionMostSecure|PCRProfileOptionNoDiscreteTPMResetMitigation) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionMostSecure can only be used on its own`) + + _, err := profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionMostSecure can only be used on its own`) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileTrustCAsForBootCode(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionTrustCAsForBootCode) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileTrustCAsForVARSuppliedDrivers(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionTrustCAsForVARSuppliedDrivers) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileTrustCAsForVARSuppliedDriversAndBootCode(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionTrustCAsForBootCode|PCRProfileOptionTrustCAsForVARSuppliedDrivers) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultDistrustVARSuppliedNonHostCode(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, AuthorityTrustBootCode | AuthorityTrustDrivers}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionDistrustVARSuppliedNonHostCode) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileDefaultDistrustVARSuppliedNonHostCodeNoDriversAndAppsProfileSupport(c *C) { + restore := MockKnownCAs(AuthorityTrustDataSet{ + {internal_efi.MSUefiCA2011, 0}, + {internal_efi.MSUefiCA2023, AuthorityTrustBootCode | AuthorityTrustDrivers}, + }) + defer restore() + + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionDistrustVARSuppliedNonHostCode) + + visitor := new(mockPcrProfileOptionVisitor) + err := profile.ApplyOptionTo(visitor) + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionDistrustVARSuppliedNonHostCode cannot be used: PCR 0x00000002 is required, but is unsupported`) + var err2 *UnsupportedRequiredPCRsError + c.Check(errors.As(err, &err2), testutil.IsTrue) + + _, err = profile.PCRs() + c.Check(err, ErrorMatches, `cannot select an appropriate set of TCG defined PCRs with the current options: PCRProfileOptionDistrustVARSuppliedNonHostCode cannot be used: PCR 0x00000002 is required, but is unsupported`) + c.Check(errors.As(err, &err2), testutil.IsTrue) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileNoDiscreteTPMMitigation(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionNoDiscreteTPMResetMitigation) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7, 4, 2}) +} + +func (s *profileSuite) TestWithAutoTCGPCRProfileOptions(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + profile = profile.Options(PCRProfileOptionTrustCAsForBootCode | PCRProfileOptionTrustCAsForVARSuppliedDrivers) + + visitor := new(mockPcrProfileOptionVisitor) + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.pcrs, DeepEquals, tpm2.HandleList{7}) + + pcrs, err := profile.PCRs() + c.Check(err, IsNil) + c.Check(pcrs, DeepEquals, tpm2.HandleList{7}) + + expectedProfile := WithAutoTCGPCRProfile(result, PCRProfileOptionTrustCAsForBootCode|PCRProfileOptionTrustCAsForVARSuppliedDrivers) + c.Check(profile, DeepEquals, expectedProfile) +} diff --git a/efi/preinstall/result.go b/efi/preinstall/result.go new file mode 100644 index 00000000..78a62704 --- /dev/null +++ b/efi/preinstall/result.go @@ -0,0 +1,550 @@ +// -*- 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" + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/canonical/go-tpm2" + "github.com/snapcore/secboot" +) + +// CheckResultFlags is returned from [RunChecks]. +type CheckResultFlags uint64 + +const ( + // NoPlatformFirmwareProfileSupport means that efi.WithPlatformFirmwareProfile can't + // be used to add the PCR0 profile to a policy. + NoPlatformFirmwareProfileSupport CheckResultFlags = 1 << iota + + // NoPlatformConfigProfileSupport means that a PCR1 profile cannot be added to a + // policy. + // + // Note that this will always be set because the efi package does not implement + // support for this PCR yet. + NoPlatformConfigProfileSupport + + // NoDriversAndAppsProfileSupport means that efi.WithDriversAndAppsProfile can't be used + // to add the PCR2 profile to a policy. + NoDriversAndAppsProfileSupport + + // NoDriversAndAppsConfigProfileSupport means that a PCR3 profile cannot be added to a + // policy. + // + // Note that this will always be set because the efi package does not implement + // support for this PCR yet. + NoDriversAndAppsConfigProfileSupport + + // NoBootManagerCodeProfileSupport means that efi.WithBootManagerCodeProfile can't be + // used to add the PCR4 profile to a policy. + NoBootManagerCodeProfileSupport + + // NoBootManagerConfigProfileSupport means that a PCR5 profile cannot be added to a + // policy. + // + // Note that this will always be set because the efi package does not implement + // support for this PCR yet. + NoBootManagerConfigProfileSupport + + // NoSecureBootPolicyProfileSupport means that efi.WithSecureBootPolicyProfile can't + // be used to add the PCR7 profile to a policy. + NoSecureBootPolicyProfileSupport + + // DiscreteTPMDetected indicates that a discrete TPM was detected. Discrete TPMs suffer from + // some well known attacks against the bus that it uses to communicate with the host chipset if + // an adversary has physical access, such as passive interposer attacks (which are mitigated + // against in Ubuntu by using response encryption with TPM2_Unseal), active interposer attacks + // where an adversary can modify communications as well as monitor them (for which there are no + // OS-level mitigations - whilst this can be mitigated by end-to-end integrity protection of PCR + // extends and other critical commands, and the use of TPM2_EncryptDecrypt2 rather than TPM2_Unseal + // in order to prevent the ability to modify session attributes and remove response encryption flag, + // the mitigations are required throughout the entire trust chain including the firmware, which is + // not the case today) and the ability to just desolder the device and attach it to a malicious host + // platform, for which there are obviously no software mitigations. Firmware based TPMs such as Intel + // PTT or those which run in a TEE are generally considered more secure as long as the persistent + // storage is adequately protected from reading sensitive data, modification and rollback. + // + // They also potentially suffer from reset attacks. Whilst the TCG PC Client Platform Firmware + // Profile Specification requires that the TPM and host platform cannot be reset independently, some + // platforms permit the TPM to be reset without resetting the host platform, breaking measured boot + // because it may be possible to reconstruct PCR values from software. This type of issue is a + // hardware integration bug. Even if resetting the TPM correctly resets the host platform, it may be + // possible for an adversary with physical access to lift the reset pin of the TPM in order to reset + // it independently, depending on what type of package is used - eg, this is significantly harder for + // TPMs in a QFN package than it is for TPMs in a TSSOP package - both of which are permitted as + // described in the TCG PC Client Platform TPM Profile Specification for TPM 2.0, although the QFN + // package is more likely to be found in laptops and other small computing devices. Note that it may + // be possible to provide some mitigation against reset attacks if the TPM's startup locality is not + // accessible from ring 0 code (platform firmware and privileged OS code). This is because the startup + // locality changes the initial value of PCR 0, and so a startup locality other than 0 will make it + // impossible to reconstruct the same PCR values from software as long as the startup locality cannot + // be accessed from software by the adversary. Note that this type of mitigation offers no protection + // from an adversary performing an active interposer attack as described before, as if they can control + // bus communications then they can access any locality in order to replay PCR values, so any mitigation + // provided is limited. + DiscreteTPMDetected + + // StartupLocalityNotProtected indicates that the TPM's startup locality can most likely be accessed + // from any code running at ring 0 (platform firmware and privileged OS code). This won't be set if + // DiscreteTPMDetected isn't also set. If this is set, then it is not possible to offer any mitigation + // against replaying PCR values from software as part of a reset attack. Support for not offering any + // reset attack mitigation has to be opted into with the PermitNoDiscreteTPMResetMitigation flag to + // RunChecks. + StartupLocalityNotProtected + + // VARDriversPresent indicates that value-added-retailer drivers were present, either + // because there are Driver#### load options and/or DriverOrder global variable, or + // because one or more was loaded from an option ROM contained on a PCI device. These + // are included in a PCR policy when using efi.WithDriversAndAppsProfile. Support for + // including value-added-retailer drivers has to be opted into with the + // PermitVARSuppliedDrivers flag to RunChecks. + // This check may not run if the NoDriversAndAppsProfileSupport flag is set. + // + // Note that this flag is not persisted when serializing the results. + VARDriversPresent + + // SysPrepApplicationsPresent indicates that system preparation applications were + // running as part of the pre-OS environment because there are SysPrep#### and + // SysPrepOrder global variables defined. As these aren't under the control of the OS, + // these can increase the fragility of profiles that include efi.WithBootManagerCodeProfile. + // Support for including system preparation applications has to be opted into with the + // PermitSysPrepApplications flag to RunChecks. + // This check may not run if the NoBootManagerCodeProfileSupport flag is set. + // + // Note that this flag is not persisted when serializing the results. + SysPrepApplicationsPresent + + // AbsoluteComputeActive indicates that the platform firmware is executing an endpoint + // management application called "Absolute" using the LoadImage API. If it is, this is + // measured to PCR4 as part of the OS-present environment before the OS is loaded. + // As this is a firmware component, this increases the fragility of profiles that include + // efi.WithBootManagerCodeProfile. Where possible, this firmware should be disabled. Support + // for including Absolute has to be opted into with the PermitAbsoluteComputrace flag to + // RunChecks. + // This check may not run if the NoBootManagerCodeProfileSupport flag is set. + // + // Note that this flag is not persisted when serializing the results. + AbsoluteComputraceActive + + // NotAllBootManagerCodeDigestsVerified indicates that the checks for efi.WithBootManagerCodeProfile + // was not able to verify all of the EV_EFI_BOOT_SERVICES_APPLICATION digests that appear in the + // log to ensure that they contain an Authenticode digest that matches a boot component used during + // the current boot. If this is set, it means that not all boot components were supplied to RunChecks. + // Support for not verifying all EV_EFI_BOOT_SERVICES_APPLICATION digests has to opted into with the + // PermitNotVerifyingAllBootManagerCodeDigests flag to RunChecks. + // This check may not run if the NoBootManagerCodeProfileSupport flag is set. + // + // Note that this flag is not persisted when serializing the results. + NotAllBootManagerCodeDigestsVerified + + // RunningInVirtualMachine indicates that the OS is running in a virtual machine. As parts + // of the TCB, such as the initial firmware code and the vTPM are under the control of the host + // environment, this configuration offers little benefit other than for testing - particularly + // in CI environments. If this is set, no checks for platform firmware protections were + // performed. Support for virtual machines has to be opted into with the PermitVirtualMachine flag + // to RunChecks. + // + // Note that this flag is not persisted when serializing the results. + RunningInVirtualMachine + + // WeakSecureBootAlgorithms indicates that weak algorithms were detected during secure boot verification, + // such as authenticating a pre-OS binary with SHA1, or with a CA with a 1024-bit RSA public key, or because + // the signing key used to sign the initial boot loader uses a 1024-bit RSA key. This does have some + // limitations because the TCG log doesn't indicate the properties of the actual signing certificate of + // the algorithms used to sign each binary, so it's not possible to verify the signing keys for components + // outside of the OS control. Support for weak secure boot algorithms has to be opted into with the + // PermitWeakSecureBootAlgorithms flag to RunChecks. + // This check may not run if the NoSecureBootPolicyProfileSupport flag is set. + // + // Note that this flag is not persisted when serializing the results. + WeakSecureBootAlgorithmsDetected + + // PreOSVerificationUsingDigestDetected indicates that pre-OS components were verified by the + // use of a digest hardcoded in the authorized signature database as opposed to a X.509 certificate. + // Support for this has to be opted into with the PermitPreOSVerificationUsingDigests flag to + // RunChecks, as it implies that db has to change with each update to certain firmware components. + // This check may not run if the NoSecureBootPolicyProfileSupport flag is set. + // + // Note that this flag is not persisted when serializing the results. + PreOSVerificationUsingDigestsDetected +) + +var checkResultFlagToIDStringMap = map[CheckResultFlags]string{ + NoPlatformFirmwareProfileSupport: "no-platform-firmware-profile-support", + NoPlatformConfigProfileSupport: "no-platform-config-profile-support", + NoDriversAndAppsProfileSupport: "no-drivers-and-apps-profile-support", + NoDriversAndAppsConfigProfileSupport: "no-drivers-and-apps-config-profile-support", + NoBootManagerCodeProfileSupport: "no-boot-manager-code-profile-support", + NoBootManagerConfigProfileSupport: "no-boot-manager-config-profile-support", + NoSecureBootPolicyProfileSupport: "no-secure-boot-policy-profile-support", + DiscreteTPMDetected: "discrete-tpm-detected", + StartupLocalityNotProtected: "startup-locality-not-protected", +} + +var checkNonPersistentResultFlagToIDStringMap = map[CheckResultFlags]string{ + VARDriversPresent: "var-drivers-present", + SysPrepApplicationsPresent: "sysprep-apps-present", + AbsoluteComputraceActive: "absolute-active", + NotAllBootManagerCodeDigestsVerified: "not-all-boot-manager-code-digests-verified", + RunningInVirtualMachine: "running-in-vm", + WeakSecureBootAlgorithmsDetected: "weak-secure-boot-algs-detected", + PreOSVerificationUsingDigestsDetected: "pre-os-verification-using-digests-detected", +} + +var checkResultFlagFromIDStringMap = map[string]CheckResultFlags{ + "no-platform-firmware-profile-support": NoPlatformFirmwareProfileSupport, + "no-platform-config-profile-support": NoPlatformConfigProfileSupport, + "no-drivers-and-apps-profile-support": NoDriversAndAppsProfileSupport, + "no-drivers-and-apps-config-profile-support": NoDriversAndAppsConfigProfileSupport, + "no-boot-manager-code-profile-support": NoBootManagerCodeProfileSupport, + "no-boot-manager-config-profile-support": NoBootManagerConfigProfileSupport, + "no-secure-boot-policy-profile-support": NoSecureBootPolicyProfileSupport, + "discrete-tpm-detected": DiscreteTPMDetected, + "startup-locality-not-protected": StartupLocalityNotProtected, +} + +type x509CertificateIdJSON struct { + Subject []byte `json:"subject"` + SubjectKeyId []byte `json:"subject-key-id"` + PublicKeyAlgorithm string `json:"pubkey-algorithm"` + + Issuer []byte `json:"issuer"` + AuthorityKeyId []byte `json:"authority-key-id"` + SignatureAlgorithm string `json:"signature-algorithm"` +} + +func newX509CertificateIdJSON(cert *X509CertificateID) (*x509CertificateIdJSON, error) { + out := &x509CertificateIdJSON{ + Subject: cert.subject, + SubjectKeyId: cert.subjectKeyId, + Issuer: cert.issuer, + AuthorityKeyId: cert.authorityKeyId, + } + + switch cert.publicKeyAlgorithm { + case x509.RSA: + out.PublicKeyAlgorithm = "RSA" + default: + return nil, fmt.Errorf("unrecognized public key algorithm %q", cert.publicKeyAlgorithm) + } + + switch cert.signatureAlgorithm { + case x509.SHA256WithRSA: + out.SignatureAlgorithm = "SHA256-RSA" + case x509.SHA384WithRSA: + out.SignatureAlgorithm = "SHA384-RSA" + case x509.SHA512WithRSA: + out.SignatureAlgorithm = "SHA512-RSA" + case x509.SHA256WithRSAPSS: + out.SignatureAlgorithm = "SHA256-RSAPSS" + case x509.SHA384WithRSAPSS: + out.SignatureAlgorithm = "SHA384-RSAPSS" + case x509.SHA512WithRSAPSS: + out.SignatureAlgorithm = "SHA512-RSAPSS" + default: + return nil, fmt.Errorf("unrecognized signature algorithm %v", cert.signatureAlgorithm) + } + + return out, nil +} + +func (id *x509CertificateIdJSON) toPublic() (*X509CertificateID, error) { + out := &X509CertificateID{ + subject: id.Subject, + subjectKeyId: id.SubjectKeyId, + issuer: id.Issuer, + authorityKeyId: id.AuthorityKeyId, + } + + switch id.PublicKeyAlgorithm { + case "RSA": + out.publicKeyAlgorithm = x509.RSA + default: + return nil, fmt.Errorf("unrecognized public key algorithm %q", id.PublicKeyAlgorithm) + } + + switch id.SignatureAlgorithm { + case "SHA256-RSA": + out.signatureAlgorithm = x509.SHA256WithRSA + case "SHA384-RSA": + out.signatureAlgorithm = x509.SHA384WithRSA + case "SHA512-RSA": + out.signatureAlgorithm = x509.SHA512WithRSA + case "SHA256-RSAPSS": + out.signatureAlgorithm = x509.SHA256WithRSAPSS + case "SHA384-RSAPSS": + out.signatureAlgorithm = x509.SHA384WithRSAPSS + case "SHA512-RSAPSS": + out.signatureAlgorithm = x509.SHA512WithRSAPSS + default: + return nil, fmt.Errorf("unrecognized signature algorithm %q", id.SignatureAlgorithm) + } + + return out, nil +} + +type checkResultJSON struct { + PCRAlg secboot.HashAlg `json:"pcr-alg"` + UsedSecureBootCAs []*X509CertificateID `json:"used-secure-boot-cas"` + Flags []string `json:"flags"` +} + +func newCheckResultJSON(r *CheckResult) (*checkResultJSON, error) { + out := new(checkResultJSON) + out.PCRAlg = secboot.HashAlg(r.PCRAlg.GetHash()) + if out.PCRAlg == secboot.HashAlg(0) { + return nil, errors.New("invalid PCR algorithm") + } + + out.UsedSecureBootCAs = r.UsedSecureBootCAs + + for i := 0; i < 64; i++ { + if r.Flags&CheckResultFlags(1< 0 { + if str, exists := checkResultFlagToIDStringMap[CheckResultFlags(1< 0 { + str, exists := checkResultFlagToIDStringMap[CheckResultFlags(1< 0 { + fmt.Fprintf(w, "- Warnings:\n") + for i := 0; i < len(r.Warnings.Errs); i++ { + fmt.Fprintf(w, "%s\n", indentLines(2, "- "+r.Warnings.Errs[i].Error())) + } + } + return w.String() +} + +// MarshalJSON implements [json.Marshaler]. +func (r CheckResult) MarshalJSON() ([]byte, error) { + j, err := newCheckResultJSON(&r) + if err != nil { + return nil, fmt.Errorf("cannot encode CheckResult: %w", err) + } + return json.Marshal(j) +} + +// UnmarshalJSON implements [json.Unmarshaler]. +func (r *CheckResult) UnmarshalJSON(data []byte) error { + var j *checkResultJSON + if err := json.Unmarshal(data, &j); err != nil { + return err + } + + pub, err := j.toPublic() + if err != nil { + return fmt.Errorf("cannot decode CheckResult: %w", err) + } + + *r = *pub + return nil +} diff --git a/efi/preinstall/result_test.go b/efi/preinstall/result_test.go new file mode 100644 index 00000000..fb6b96ad --- /dev/null +++ b/efi/preinstall/result_test.go @@ -0,0 +1,320 @@ +// -*- 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 ( + "encoding/json" + "fmt" + + "github.com/canonical/go-tpm2" + . "github.com/snapcore/secboot/efi/preinstall" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +type resultSuite struct{} + +var _ = Suite(&resultSuite{}) + +func (s *resultSuite) TestCheckResultMarshalJSON(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONSHA384(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA384, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha384\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONDifferentCA(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"ME4xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFk1pY3Jvc29mdCBVRUZJIENBIDIwMjM=\",\"subject-key-id\":\"gaprMkTJNbzg1mKK85gnQh4ySX0=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MFoxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBSU0EgRGV2aWNlcyBSb290IENBIDIwMjE=\",\"authority-key-id\":\"hESGBgCYPyyqs8WJ86wuyeadCQM=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONMultipleCAs(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023)), + }, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"},{\"subject\":\"ME4xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFk1pY3Jvc29mdCBVRUZJIENBIDIwMjM=\",\"subject-key-id\":\"gaprMkTJNbzg1mKK85gnQh4ySX0=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MFoxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBSU0EgRGV2aWNlcyBSb290IENBIDIwMjE=\",\"authority-key-id\":\"hESGBgCYPyyqs8WJ86wuyeadCQM=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONWithNonPersistentFlags(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | + DiscreteTPMDetected | VARDriversPresent | SysPrepApplicationsPresent | AbsoluteComputraceActive | NotAllBootManagerCodeDigestsVerified | + RunningInVirtualMachine | WeakSecureBootAlgorithmsDetected | PreOSVerificationUsingDigestsDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONNoPlatformFirmareProfileSupport(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformFirmwareProfileSupport | NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-firmware-profile-support\",\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONNoDriversAndAppsProfileSupport(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONNoBootManagerCodeProfileSupport(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-code-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONNoSecureBootPolicyProfileSupport(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | NoSecureBootPolicyProfileSupport | DiscreteTPMDetected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":null,\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"no-secure-boot-policy-profile-support\",\"discrete-tpm-detected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONStartupLocalityNotProtected(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected | StartupLocalityNotProtected, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-code-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\",\"startup-locality-not-protected\"]}")) +} + +func (s *resultSuite) TestCheckResultMarshalJSONNoDiscreteTPM(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport, + } + data, err := json.Marshal(result) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-code-profile-support\",\"no-boot-manager-config-profile-support\"]}")) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSON(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONSHA384(c *C) { + data := []byte("{\"pcr-alg\":\"sha384\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA384, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONDifferentCA(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"ME4xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFk1pY3Jvc29mdCBVRUZJIENBIDIwMjM=\",\"subject-key-id\":\"gaprMkTJNbzg1mKK85gnQh4ySX0=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MFoxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBSU0EgRGV2aWNlcyBSb290IENBIDIwMjE=\",\"authority-key-id\":\"hESGBgCYPyyqs8WJ86wuyeadCQM=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONMultipleCAs(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"},{\"subject\":\"ME4xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFk1pY3Jvc29mdCBVRUZJIENBIDIwMjM=\",\"subject-key-id\":\"gaprMkTJNbzg1mKK85gnQh4ySX0=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MFoxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBSU0EgRGV2aWNlcyBSb290IENBIDIwMjE=\",\"authority-key-id\":\"hESGBgCYPyyqs8WJ86wuyeadCQM=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert2023)), + }, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONNoPlatformFirmareProfileSupport(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-firmware-profile-support\",\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformFirmwareProfileSupport | NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONNoDriversAndAppsProfileSupport(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONNoBootManagerCodeProfileSupport(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-code-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONNoSecureBootPolicyProfileSupport(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":null,\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"no-secure-boot-policy-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | NoSecureBootPolicyProfileSupport | DiscreteTPMDetected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONStartupLocalityNotProtected(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-code-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\",\"startup-locality-not-protected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected | StartupLocalityNotProtected, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONNoDiscreteTPM(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-code-profile-support\",\"no-boot-manager-config-profile-support\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), IsNil) + c.Check(result, DeepEquals, &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerCodeProfileSupport | NoBootManagerConfigProfileSupport, + }) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONUnrecognizedPCRAlg(c *C) { + data := []byte("{\"pcr-alg\":\"sha3-256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), ErrorMatches, `cannot decode CheckResult: unrecognized PCR algorithm`) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONCorruptSecureBootCA(c *C) { + corruptCA, err := json.Marshal(NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))) + c.Assert(err, IsNil) + corruptCA[10] = ';' + data := []byte(fmt.Sprintf("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[%s],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\"]}", corruptCA)) + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), ErrorMatches, `invalid character ';' after object key`) +} + +func (s *resultSuite) TestCheckResultUnmarshalJSONUnrecognizedFlags(c *C) { + data := []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\",\"discrete-tpm-detected\",\"var-drivers-present\"]}") + + var result *CheckResult + c.Assert(json.Unmarshal(data, &result), ErrorMatches, `cannot decode CheckResult: unrecognized flag \"var-drivers-present\"`) +} + +func (s *resultSuite) TestCheckResultString(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected | VARDriversPresent | AbsoluteComputraceActive, + } + c.Check(result.String(), Equals, ` +EFI based TPM protected FDE test support results: +- Best PCR algorithm: TPM_ALG_SHA256 +- Secure boot CAs used for verification: + 1: subject=CN=Microsoft Corporation UEFI CA 2011,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US, SKID=0x13adbf4309bd82709c8cd54f316ed522988a1bd4, pubkeyAlg=RSA, issuer=CN=Microsoft Corporation Third Party Marketplace Root,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US, AKID=0x45665243e17e5811bfd64e9e2355083b3a226aa8, sigAlg=SHA256-RSA +- Flags: no-platform-config-profile-support,no-drivers-and-apps-config-profile-support,no-boot-manager-config-profile-support,discrete-tpm-detected,var-drivers-present,absolute-active +`) +} diff --git a/efi/preinstall/testdata/MicrosoftUefiCA2023.crt b/efi/preinstall/testdata/MicrosoftUefiCA2023.crt new file mode 100644 index 00000000..5c7ad84f --- /dev/null +++ b/efi/preinstall/testdata/MicrosoftUefiCA2023.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgITMwAAABY2vzaJnxV1zAAAAAAAFjANBgkqhkiG9w0BAQsF +ADBaMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MSswKQYDVQQDEyJNaWNyb3NvZnQgUlNBIERldmljZXMgUm9vdCBDQSAyMDIxMB4X +DTIzMDYxMzE5MjE0N1oXDTM4MDYxMzE5MzE0N1owTjELMAkGA1UEBhMCVVMxHjAc +BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEfMB0GA1UEAxMWTWljcm9zb2Z0 +IFVFRkkgQ0EgMjAyMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL0i +Kq7vGjGFE3hRp5v9/HjRY7gam2P1EgbbS0E1am+r9WoEzJfPu9QICRphOg3ms6BG +/wmt3oAk3BKA8l/ZFu3iQp3NL01hAmGKHEsdGGI5hpdxrT5/XXETS+kqAMG+1bcA +n15lsiwa/3Tt6oPSOYkzNXN9oKL6QORmUFiq/IfoXCCDNOyr4gvFXz7/SCsRkSbv +GG5XxZ8Yc5nv4Wp0K7svf1COHdo9drYE5cwuEMeDG4Oj5KUTE3FuM3ijqDzsSCZe +x8ZeDYeaqsxVNIGtnZD15pZjpugHIBfIkx7SrqTcrn1Zv4heYgyuW/IpQFYdJkDe +haatVtHPVUd2X5w52wMCAwEAAaOCAW0wggFpMA4GA1UdDwEB/wQEAwIBhjAQBgkr +BgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQUgaprMkTJNbzg1mKK85gnQh4ySX0wGQYJ +KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSERIYGAJg/LKqzxYnzrC7J5p0JAzBlBgNVHR8EXjBcMFqgWKBWhlRodHRw +Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBSU0El +MjBEZXZpY2VzJTIwUm9vdCUyMENBJTIwMjAyMS5jcmwwcgYIKwYBBQUHAQEEZjBk +MGIGCCsGAQUFBzAChlZodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Nl +cnRzL01pY3Jvc29mdCUyMFJTQSUyMERldmljZXMlMjBSb290JTIwQ0ElMjAyMDIx +LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAB2ATKlOHEg8a81oUlRfl2NeVVJuLDt2R +pe3HXUdQk0W3lYhfFxlBY3a1grCoxZ2ZFTaJSb4Swmb7gwywgc7lpKvCoJrr9Qc8 +/iH4mtwZIQyeJCzRXKIWCkvr7EicsVt02wFkwuOAaqsazXcbajmat7pwRP9nlMWB +BvDLgQSTJyGZvYeIFJwicQ4LL1y+uJBUfMAevCubo1YXS5fn438TNPqwNGub9rIt +99h72CDTXKeVTE8q+eceaK/8bI/Ihj2fyNHvTRrI0fb9LXzj6EHB6ifB+44lhlqJ +phC+zuOPpXvEGqDodZD9IbDBo8UWI148zi/+jJi/CFz2ucWyPLbMyOx/0nd0y+3z +lsmLjRwqiQ+jj73OKoVGmiOij0LAmdbqhR9hGb4WNbd1oJWAZQaH1As1yMSqDs6i +CmNgyksrXCcEgq8+WIN6WthnPxBT9QwW9yZLioC5xR+g3tjTYUQURaf1q5qIF/23 +lFQCi+S3U6E+jZ5QgqgA4HiUG76zxDAfsg7b8EaQweZX/nzBcLIcS2TZEAMbNPtm +z4JunkCoETfyZYshCa88k2I987yD3T9VkBXSMa8R5/jKoILhuc+zV5PHVTesf0G/ +H5Y88yaU+djSVSSKirZB8OAWwCOSjHEKTGoNGVX3OpySIZah1fgKjJ2/yevKiEL8 +S7Tv/ycwIWE= +-----END CERTIFICATE----- diff --git a/go.mod b/go.mod index 720f623c..c5774ef5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb - github.com/canonical/go-efilib v1.3.1 + github.com/canonical/go-efilib v1.4.1 github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 github.com/canonical/go-tpm2 v1.7.6 diff --git a/go.sum b/go.sum index 1ee5d1bc..5deb1484 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb h1:+kA/9oHTqUx4P08ywKvmd7a1wOL3RLTrE0K958C15x8= github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb/go.mod h1:6j8Sw3dwYVcBXltEeGklDoK/8UJVJNQPUkg1ZdQUgbk= github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4/go.mod h1:9Sr9kd7IhQPYqaU5nut8Ky97/CtlhHDzQncQnrULgDM= -github.com/canonical/go-efilib v1.3.1 h1:KnVlqrKn0ZDGAbgQt9tke5cvtqNRCmpEp0v7RGUVpqs= -github.com/canonical/go-efilib v1.3.1/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= +github.com/canonical/go-efilib v1.4.1 h1:/VMNCypz+iVmnNuMcsm7WvmDMI1ObkEP2W1h8Ls7OyM= +github.com/canonical/go-efilib v1.4.1/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 h1:ZE2XMRFHcwlib3uU9is37+pKkkMloVoEPWmgQ6GK1yo= github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= diff --git a/internal/efi/known_secureboot_cas.go b/internal/efi/known_secureboot_cas.go index 00763291..aa7c13df 100644 --- a/internal/efi/known_secureboot_cas.go +++ b/internal/efi/known_secureboot_cas.go @@ -58,6 +58,28 @@ var ( 0xd5, 0x4f, 0x31, 0x6e, 0xd5, 0x22, 0x98, 0x8a, 0x1b, 0xd4, }, PublicKeyAlgorithm: x509.RSA, + Issuer: []byte{ + 0x30, 0x81, 0x91, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, + 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, + 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, + 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, 0x1c, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, + 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x3b, 0x30, + 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x4d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, + 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x54, 0x68, 0x69, 0x72, 0x64, 0x20, 0x50, 0x61, 0x72, 0x74, + 0x79, 0x20, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, + }, + AuthorityKeyId: []byte{ + 0x45, 0x66, 0x52, 0x43, 0xe1, 0x7e, 0x58, 0x11, 0xbf, 0xd6, + 0x4e, 0x9e, 0x23, 0x55, 0x08, 0x3b, 0x3a, 0x22, 0x6a, 0xa8, + }, + SignatureAlgorithm: x509.SHA256WithRSA, } // MSUefiCA2023 corresponds to the 2023 Microsoft UEFI CA, which will eventually @@ -79,5 +101,22 @@ var ( 0x62, 0x8a, 0xf3, 0x98, 0x27, 0x42, 0x1e, 0x32, 0x49, 0x7d, }, PublicKeyAlgorithm: x509.RSA, + Issuer: []byte{ + 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, + 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x2b, 0x30, + 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x4d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x52, 0x53, + 0x41, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, + 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, + 0x32, 0x31, + }, + AuthorityKeyId: []byte{ + 0x84, 0x44, 0x86, 0x06, 0x00, 0x98, 0x3f, 0x2c, 0xaa, 0xb3, + 0xc5, 0x89, 0xf3, 0xac, 0x2e, 0xc9, 0xe6, 0x9d, 0x09, 0x03, + }, + SignatureAlgorithm: x509.SHA256WithRSA, } ) From 69645954937fe50a6f1440d4f78d971121f60265 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 3 Dec 2024 12:56:38 +0000 Subject: [PATCH 2/6] preinstall: update indentLines to break earlier on io.EOF --- efi/preinstall/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/efi/preinstall/errors.go b/efi/preinstall/errors.go index 2e89bd51..579d4fdc 100644 --- a/efi/preinstall/errors.go +++ b/efi/preinstall/errors.go @@ -38,7 +38,6 @@ func indentLines(n int, str string) string { br := bufio.NewReader(r) for { line, err := br.ReadString('\n') - fmt.Fprintf(w, "%*s%s", n, "", line) if err == io.EOF { break } @@ -46,6 +45,7 @@ func indentLines(n int, str string) string { fmt.Fprintf(w, "%*serror occurred whilst indenting: %v", n, "", err) break } + fmt.Fprintf(w, "%*s%s", n, "", line) } return w.String() } From f6fae02c56cb7599dc7f2f99b65b7ba47cb112fa Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 3 Dec 2024 13:06:16 +0000 Subject: [PATCH 3/6] preinstall: Move X509CertificateID types to their own file --- efi/preinstall/result.go | 192 ---------------------------------- efi/preinstall/x509id.go | 217 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 192 deletions(-) create mode 100644 efi/preinstall/x509id.go diff --git a/efi/preinstall/result.go b/efi/preinstall/result.go index 78a62704..af8fcf2a 100644 --- a/efi/preinstall/result.go +++ b/efi/preinstall/result.go @@ -22,8 +22,6 @@ package preinstall import ( "bytes" "crypto" - "crypto/x509" - "crypto/x509/pkix" "encoding/json" "errors" "fmt" @@ -227,86 +225,6 @@ var checkResultFlagFromIDStringMap = map[string]CheckResultFlags{ "startup-locality-not-protected": StartupLocalityNotProtected, } -type x509CertificateIdJSON struct { - Subject []byte `json:"subject"` - SubjectKeyId []byte `json:"subject-key-id"` - PublicKeyAlgorithm string `json:"pubkey-algorithm"` - - Issuer []byte `json:"issuer"` - AuthorityKeyId []byte `json:"authority-key-id"` - SignatureAlgorithm string `json:"signature-algorithm"` -} - -func newX509CertificateIdJSON(cert *X509CertificateID) (*x509CertificateIdJSON, error) { - out := &x509CertificateIdJSON{ - Subject: cert.subject, - SubjectKeyId: cert.subjectKeyId, - Issuer: cert.issuer, - AuthorityKeyId: cert.authorityKeyId, - } - - switch cert.publicKeyAlgorithm { - case x509.RSA: - out.PublicKeyAlgorithm = "RSA" - default: - return nil, fmt.Errorf("unrecognized public key algorithm %q", cert.publicKeyAlgorithm) - } - - switch cert.signatureAlgorithm { - case x509.SHA256WithRSA: - out.SignatureAlgorithm = "SHA256-RSA" - case x509.SHA384WithRSA: - out.SignatureAlgorithm = "SHA384-RSA" - case x509.SHA512WithRSA: - out.SignatureAlgorithm = "SHA512-RSA" - case x509.SHA256WithRSAPSS: - out.SignatureAlgorithm = "SHA256-RSAPSS" - case x509.SHA384WithRSAPSS: - out.SignatureAlgorithm = "SHA384-RSAPSS" - case x509.SHA512WithRSAPSS: - out.SignatureAlgorithm = "SHA512-RSAPSS" - default: - return nil, fmt.Errorf("unrecognized signature algorithm %v", cert.signatureAlgorithm) - } - - return out, nil -} - -func (id *x509CertificateIdJSON) toPublic() (*X509CertificateID, error) { - out := &X509CertificateID{ - subject: id.Subject, - subjectKeyId: id.SubjectKeyId, - issuer: id.Issuer, - authorityKeyId: id.AuthorityKeyId, - } - - switch id.PublicKeyAlgorithm { - case "RSA": - out.publicKeyAlgorithm = x509.RSA - default: - return nil, fmt.Errorf("unrecognized public key algorithm %q", id.PublicKeyAlgorithm) - } - - switch id.SignatureAlgorithm { - case "SHA256-RSA": - out.signatureAlgorithm = x509.SHA256WithRSA - case "SHA384-RSA": - out.signatureAlgorithm = x509.SHA384WithRSA - case "SHA512-RSA": - out.signatureAlgorithm = x509.SHA512WithRSA - case "SHA256-RSAPSS": - out.signatureAlgorithm = x509.SHA256WithRSAPSS - case "SHA384-RSAPSS": - out.signatureAlgorithm = x509.SHA384WithRSAPSS - case "SHA512-RSAPSS": - out.signatureAlgorithm = x509.SHA512WithRSAPSS - default: - return nil, fmt.Errorf("unrecognized signature algorithm %q", id.SignatureAlgorithm) - } - - return out, nil -} - type checkResultJSON struct { PCRAlg secboot.HashAlg `json:"pcr-alg"` UsedSecureBootCAs []*X509CertificateID `json:"used-secure-boot-cas"` @@ -361,116 +279,6 @@ func (r checkResultJSON) toPublic() (*CheckResult, error) { return out, nil } -// X509CertificateID corresponds to the identity of a X.509 certificate. -// It is JSON serializable and avoids the need to persist an entire certificate -// when we only use the parts that identify it. -type X509CertificateID struct { - subject []byte - subjectKeyId []byte - publicKeyAlgorithm x509.PublicKeyAlgorithm - - issuer []byte - authorityKeyId []byte - signatureAlgorithm x509.SignatureAlgorithm -} - -func newX509CertificateID(cert *x509.Certificate) *X509CertificateID { - return &X509CertificateID{ - subject: cert.RawSubject, - subjectKeyId: cert.SubjectKeyId, - publicKeyAlgorithm: cert.PublicKeyAlgorithm, - issuer: cert.RawIssuer, - authorityKeyId: cert.AuthorityKeyId, - signatureAlgorithm: cert.SignatureAlgorithm, - } -} - -// Subject returns the readable form of the certificate's subject. -func (id *X509CertificateID) Subject() pkix.Name { - rdns, err := parseName(id.subject) - if err != nil { - return pkix.Name{} - } - - var res pkix.Name - res.FillFromRDNSequence(rdns) - return res -} - -// RawSubject returns the certificate's raw DER encoded subject. -// It implements [github.com/canonical/go-efilib.X509CertID.RawSubject]. -func (id *X509CertificateID) RawSubject() []byte { - return id.subject -} - -// SubjectKeyID returns the ID of the subject's public key. It implements -// [github.com/canonical/go-efilib.X509CertID.SubjectKeyID]. -func (id *X509CertificateID) SubjectKeyId() []byte { - return id.subjectKeyId -} - -// PublicKeyAlgorithm returns the algorithm of the public key. It implements -// [github.com/canonical/go-efilib.X509CertID.PublicKeyAlgorithm]. -func (id *X509CertificateID) PublicKeyAlgorithm() x509.PublicKeyAlgorithm { - return id.publicKeyAlgorithm -} - -// Issuer returns the readable form of the certificate's issuer. -func (id *X509CertificateID) Issuer() pkix.Name { - rdns, err := parseName(id.issuer) - if err != nil { - return pkix.Name{} - } - - var res pkix.Name - res.FillFromRDNSequence(rdns) - return res -} - -// RawIssuer returns the certificate's raw DER encoded issuer. -// It implements [github.com/canonical/go-efilib.X509CertID.RawIssuer]. -func (id *X509CertificateID) RawIssuer() []byte { - return id.issuer -} - -// AuthorityKeyID returns the ID of the issuer's public key. It implements -// [github.com/canonical/go-efilib.X509CertID.AuthorityKeyID]. -func (id *X509CertificateID) AuthorityKeyId() []byte { - return id.authorityKeyId -} - -// SignatureAlgorithm indicates the algorithm that the issuer used -// to sign the subject certificate. It implements -// [github.com/canonical/go-efilib.X509CertID.SignatureAlgorithm]. -func (id *X509CertificateID) SignatureAlgorithm() x509.SignatureAlgorithm { - return id.signatureAlgorithm -} - -// MarshalJSON implements [json.Marshaler]. -func (id X509CertificateID) MarshalJSON() ([]byte, error) { - j, err := newX509CertificateIdJSON(&id) - if err != nil { - return nil, fmt.Errorf("cannot encode X509CertificateID: %w", err) - } - return json.Marshal(j) -} - -// UnmarshalJSON implements [json.Unmarshaler]. -func (id *X509CertificateID) UnmarshalJSON(data []byte) error { - var j *x509CertificateIdJSON - if err := json.Unmarshal(data, &j); err != nil { - return err - } - - pub, err := j.toPublic() - if err != nil { - return fmt.Errorf("cannot decode X509CertificateID: %w", err) - } - - *id = *pub - return nil -} - // CheckResult is returned from [RunChecks] when it completes successfully. // It is JSON serializable, although some flags and fields are omitted. type CheckResult struct { diff --git a/efi/preinstall/x509id.go b/efi/preinstall/x509id.go new file mode 100644 index 00000000..6a9192c9 --- /dev/null +++ b/efi/preinstall/x509id.go @@ -0,0 +1,217 @@ +// -*- 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 ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "fmt" +) + +type x509CertificateIdJSON struct { + Subject []byte `json:"subject"` + SubjectKeyId []byte `json:"subject-key-id"` + PublicKeyAlgorithm string `json:"pubkey-algorithm"` + + Issuer []byte `json:"issuer"` + AuthorityKeyId []byte `json:"authority-key-id"` + SignatureAlgorithm string `json:"signature-algorithm"` +} + +func newX509CertificateIdJSON(cert *X509CertificateID) (*x509CertificateIdJSON, error) { + out := &x509CertificateIdJSON{ + Subject: cert.subject, + SubjectKeyId: cert.subjectKeyId, + Issuer: cert.issuer, + AuthorityKeyId: cert.authorityKeyId, + } + + switch cert.publicKeyAlgorithm { + case x509.RSA: + out.PublicKeyAlgorithm = "RSA" + default: + return nil, fmt.Errorf("unrecognized public key algorithm %q", cert.publicKeyAlgorithm) + } + + switch cert.signatureAlgorithm { + case x509.SHA256WithRSA: + out.SignatureAlgorithm = "SHA256-RSA" + case x509.SHA384WithRSA: + out.SignatureAlgorithm = "SHA384-RSA" + case x509.SHA512WithRSA: + out.SignatureAlgorithm = "SHA512-RSA" + case x509.SHA256WithRSAPSS: + out.SignatureAlgorithm = "SHA256-RSAPSS" + case x509.SHA384WithRSAPSS: + out.SignatureAlgorithm = "SHA384-RSAPSS" + case x509.SHA512WithRSAPSS: + out.SignatureAlgorithm = "SHA512-RSAPSS" + default: + return nil, fmt.Errorf("unrecognized signature algorithm %v", cert.signatureAlgorithm) + } + + return out, nil +} + +func (id *x509CertificateIdJSON) toPublic() (*X509CertificateID, error) { + out := &X509CertificateID{ + subject: id.Subject, + subjectKeyId: id.SubjectKeyId, + issuer: id.Issuer, + authorityKeyId: id.AuthorityKeyId, + } + + switch id.PublicKeyAlgorithm { + case "RSA": + out.publicKeyAlgorithm = x509.RSA + default: + return nil, fmt.Errorf("unrecognized public key algorithm %q", id.PublicKeyAlgorithm) + } + + switch id.SignatureAlgorithm { + case "SHA256-RSA": + out.signatureAlgorithm = x509.SHA256WithRSA + case "SHA384-RSA": + out.signatureAlgorithm = x509.SHA384WithRSA + case "SHA512-RSA": + out.signatureAlgorithm = x509.SHA512WithRSA + case "SHA256-RSAPSS": + out.signatureAlgorithm = x509.SHA256WithRSAPSS + case "SHA384-RSAPSS": + out.signatureAlgorithm = x509.SHA384WithRSAPSS + case "SHA512-RSAPSS": + out.signatureAlgorithm = x509.SHA512WithRSAPSS + default: + return nil, fmt.Errorf("unrecognized signature algorithm %q", id.SignatureAlgorithm) + } + + return out, nil +} + +// X509CertificateID corresponds to the identity of a X.509 certificate. +// It is JSON serializable and avoids the need to persist an entire certificate +// when we only use the parts that identify it. +type X509CertificateID struct { + subject []byte + subjectKeyId []byte + publicKeyAlgorithm x509.PublicKeyAlgorithm + + issuer []byte + authorityKeyId []byte + signatureAlgorithm x509.SignatureAlgorithm +} + +func newX509CertificateID(cert *x509.Certificate) *X509CertificateID { + return &X509CertificateID{ + subject: cert.RawSubject, + subjectKeyId: cert.SubjectKeyId, + publicKeyAlgorithm: cert.PublicKeyAlgorithm, + issuer: cert.RawIssuer, + authorityKeyId: cert.AuthorityKeyId, + signatureAlgorithm: cert.SignatureAlgorithm, + } +} + +// Subject returns the readable form of the certificate's subject. +func (id *X509CertificateID) Subject() pkix.Name { + rdns, err := parseName(id.subject) + if err != nil { + return pkix.Name{} + } + + var res pkix.Name + res.FillFromRDNSequence(rdns) + return res +} + +// RawSubject returns the certificate's raw DER encoded subject. +// It implements [github.com/canonical/go-efilib.X509CertID.RawSubject]. +func (id *X509CertificateID) RawSubject() []byte { + return id.subject +} + +// SubjectKeyID returns the ID of the subject's public key. It implements +// [github.com/canonical/go-efilib.X509CertID.SubjectKeyID]. +func (id *X509CertificateID) SubjectKeyId() []byte { + return id.subjectKeyId +} + +// PublicKeyAlgorithm returns the algorithm of the public key. It implements +// [github.com/canonical/go-efilib.X509CertID.PublicKeyAlgorithm]. +func (id *X509CertificateID) PublicKeyAlgorithm() x509.PublicKeyAlgorithm { + return id.publicKeyAlgorithm +} + +// Issuer returns the readable form of the certificate's issuer. +func (id *X509CertificateID) Issuer() pkix.Name { + rdns, err := parseName(id.issuer) + if err != nil { + return pkix.Name{} + } + + var res pkix.Name + res.FillFromRDNSequence(rdns) + return res +} + +// RawIssuer returns the certificate's raw DER encoded issuer. +// It implements [github.com/canonical/go-efilib.X509CertID.RawIssuer]. +func (id *X509CertificateID) RawIssuer() []byte { + return id.issuer +} + +// AuthorityKeyID returns the ID of the issuer's public key. It implements +// [github.com/canonical/go-efilib.X509CertID.AuthorityKeyID]. +func (id *X509CertificateID) AuthorityKeyId() []byte { + return id.authorityKeyId +} + +// SignatureAlgorithm indicates the algorithm that the issuer used +// to sign the subject certificate. It implements +// [github.com/canonical/go-efilib.X509CertID.SignatureAlgorithm]. +func (id *X509CertificateID) SignatureAlgorithm() x509.SignatureAlgorithm { + return id.signatureAlgorithm +} + +// MarshalJSON implements [json.Marshaler]. +func (id X509CertificateID) MarshalJSON() ([]byte, error) { + j, err := newX509CertificateIdJSON(&id) + if err != nil { + return nil, fmt.Errorf("cannot encode X509CertificateID: %w", err) + } + return json.Marshal(j) +} + +// UnmarshalJSON implements [json.Unmarshaler]. +func (id *X509CertificateID) UnmarshalJSON(data []byte) error { + var j *x509CertificateIdJSON + if err := json.Unmarshal(data, &j); err != nil { + return err + } + + pub, err := j.toPublic() + if err != nil { + return fmt.Errorf("cannot decode X509CertificateID: %w", err) + } + + *id = *pub + return nil +} From 7c0c6e087adca6572fe6d7e942d971885832ce94 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 3 Dec 2024 15:14:53 +0000 Subject: [PATCH 4/6] preinstall: Add test for CheckResult.String() with Warnings Also fix the implementation of RunChecksErrors.Error(). --- efi/preinstall/errors.go | 7 +++++-- efi/preinstall/result_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/efi/preinstall/errors.go b/efi/preinstall/errors.go index 579d4fdc..3e954c04 100644 --- a/efi/preinstall/errors.go +++ b/efi/preinstall/errors.go @@ -38,6 +38,10 @@ func indentLines(n int, str string) string { br := bufio.NewReader(r) for { line, err := br.ReadString('\n') + if err == io.EOF && line == "" { + break + } + fmt.Fprintf(w, "%*s%s", n, "", line) if err == io.EOF { break } @@ -45,7 +49,6 @@ func indentLines(n int, str string) string { fmt.Fprintf(w, "%*serror occurred whilst indenting: %v", n, "", err) break } - fmt.Fprintf(w, "%*s%s", n, "", line) } return w.String() } @@ -62,7 +65,7 @@ func (e *RunChecksErrors) Error() string { w := new(bytes.Buffer) fmt.Fprintf(w, "one or more errors detected:\n") for _, err := range e.Errs { - fmt.Fprintf(w, "%s\n", indentLines(2, "- "+err.Error())) + fmt.Fprintf(w, "%s\n", indentLines(0, "- "+err.Error())) } return w.String() } diff --git a/efi/preinstall/result_test.go b/efi/preinstall/result_test.go index fb6b96ad..ff87a3c9 100644 --- a/efi/preinstall/result_test.go +++ b/efi/preinstall/result_test.go @@ -21,6 +21,7 @@ package preinstall_test import ( "encoding/json" + "errors" "fmt" "github.com/canonical/go-tpm2" @@ -318,3 +319,27 @@ EFI based TPM protected FDE test support results: - Flags: no-platform-config-profile-support,no-drivers-and-apps-config-profile-support,no-boot-manager-config-profile-support,discrete-tpm-detected,var-drivers-present,absolute-active `) } + +func (s *resultSuite) TestCheckResultStringWithWarnings(c *C) { + result := CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + Flags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport | DiscreteTPMDetected | VARDriversPresent | AbsoluteComputraceActive, + Warnings: &RunChecksErrors{ + Errs: []error{ + errors.New("some error 1"), + errors.New("some error 2"), + }, + }, + } + c.Check(result.String(), Equals, ` +EFI based TPM protected FDE test support results: +- Best PCR algorithm: TPM_ALG_SHA256 +- Secure boot CAs used for verification: + 1: subject=CN=Microsoft Corporation UEFI CA 2011,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US, SKID=0x13adbf4309bd82709c8cd54f316ed522988a1bd4, pubkeyAlg=RSA, issuer=CN=Microsoft Corporation Third Party Marketplace Root,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US, AKID=0x45665243e17e5811bfd64e9e2355083b3a226aa8, sigAlg=SHA256-RSA +- Flags: no-platform-config-profile-support,no-drivers-and-apps-config-profile-support,no-boot-manager-config-profile-support,discrete-tpm-detected,var-drivers-present,absolute-active +- Warnings: + - some error 1 + - some error 2 +`) +} From 7ac516cd423357dfca2880ac7b761cdbc100efe2 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 4 Dec 2024 01:03:19 +0000 Subject: [PATCH 5/6] preinstall: rename indentLines and other changes This renames indentLines to makeIndentedListItem to make it clear that it's used to help printing out lists of things (in this context, mostly lists of errors). It also refactors it to use bufio.Scanner. Some additional test cases are added to test the formatting of RunChecksErrors, both on its own and embedded as a warning inside of CheckResult. --- efi/preinstall/errors.go | 72 ++++++++++++++++++++++++++--------- efi/preinstall/errors_test.go | 56 +++++++++++++++++++++++++++ efi/preinstall/result.go | 16 ++++---- efi/preinstall/result_test.go | 4 +- 4 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 efi/preinstall/errors_test.go diff --git a/efi/preinstall/errors.go b/efi/preinstall/errors.go index 3e954c04..45102d16 100644 --- a/efi/preinstall/errors.go +++ b/efi/preinstall/errors.go @@ -30,25 +30,61 @@ import ( internal_efi "github.com/snapcore/secboot/internal/efi" ) -// indentLines is a helper for managing indenting in nested multi-line -// errors. -func indentLines(n int, str string) string { - r := bytes.NewReader([]byte(str)) - w := new(bytes.Buffer) - br := bufio.NewReader(r) - for { - line, err := br.ReadString('\n') - if err == io.EOF && line == "" { - break - } - fmt.Fprintf(w, "%*s%s", n, "", line) - if err == io.EOF { - break +// makeIndentedListItem turns the supplied string into a list item by prepending +// the supplied marker string (which could be a bullet point or numeric character) +// to the supplied string, useful for displaying multiple errors. In a multi-line +// string, subsequent lines in the supplied string will all be aligned with the start +// of the first line after the marker. The indentation argument specifies the +// indentation of the maker, in the number of characters. +func makeIndentedListItem(indentation int, marker, str string) string { + scanner := bufio.NewScanner(bytes.NewReader([]byte(str))) + + // lastLineEndsInNewline is needed as a flag to determine whether we need + // to return our string with a newline terminator. This is because, whilst + // the default bufio.Scanner implementation, which uses bufio.ScanLines, + // returns each line of text separately, it does not return the newline + // characters. We do a bit of a hack here to intercept the bufio.ScanLines + // call to determine if the last line was terminated with a newline character. + lastLineEndsInNewline := false + scanner.Split(func(data []byte, atEOF bool) (adv int, token []byte, err error) { + adv, token, err = bufio.ScanLines(data, atEOF) + if atEOF { + switch { + case len(data) == 0: + // The last call was with data and !atEOF, so the last byte + // had to have been a newline in order to end up here. + lastLineEndsInNewline = true + case adv == len(data) && data[len(data)-1] == byte('\n'): + // The data argument contains all of the remaining data, we + // advanced to the end of it, and the last character is a + // newline. + lastLineEndsInNewline = true + } } - if err != nil { - fmt.Fprintf(w, "%*serror occurred whilst indenting: %v", n, "", err) - break + return adv, token, err + }) + + w := new(bytes.Buffer) + + // Start the first line with a hyphen, at the specified indentation. + fmt.Fprintf(w, "%*s%s ", indentation, "", marker) + firstLine := true // we treat the first and subsequent lines differently. + for scanner.Scan() { + if firstLine { + io.WriteString(w, scanner.Text()) + firstLine = false + continue } + + // Subsequent lines should be aligned with the first line. + fmt.Fprintf(w, "\n%*s%s", indentation+2, "", scanner.Text()) + } + if scanner.Err() != nil { + // If an error occurred in scanning, add the error message to our output. + fmt.Fprintf(w, "\n%*s", indentation+2, "", scanner.Err()) + } + if lastLineEndsInNewline { + io.WriteString(w, "\n") } return w.String() } @@ -65,7 +101,7 @@ func (e *RunChecksErrors) Error() string { w := new(bytes.Buffer) fmt.Fprintf(w, "one or more errors detected:\n") for _, err := range e.Errs { - fmt.Fprintf(w, "%s\n", indentLines(0, "- "+err.Error())) + io.WriteString(w, makeIndentedListItem(0, "-", err.Error())) } return w.String() } diff --git a/efi/preinstall/errors_test.go b/efi/preinstall/errors_test.go new file mode 100644 index 00000000..90199fe9 --- /dev/null +++ b/efi/preinstall/errors_test.go @@ -0,0 +1,56 @@ +// -*- 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 ( + "errors" + + . "github.com/snapcore/secboot/efi/preinstall" + . "gopkg.in/check.v1" +) + +type errorsSuite struct{} + +var _ = Suite(&errorsSuite{}) + +func (s *errorsSuite) TestRunChecksErrorsError(c *C) { + err := &RunChecksErrors{ + Errs: []error{ + errors.New("some error 1"), + errors.New(`some error 2 +across more than one line`), + errors.New("some error 3"), + errors.New(`some error 4 +which also spans across +multiple lines +`), + }, + } + + c.Check(err.Error(), Equals, `one or more errors detected: +- some error 1 +- some error 2 + across more than one line +- some error 3 +- some error 4 + which also spans across + multiple lines +`) +} diff --git a/efi/preinstall/result.go b/efi/preinstall/result.go index af8fcf2a..99b93332 100644 --- a/efi/preinstall/result.go +++ b/efi/preinstall/result.go @@ -25,6 +25,8 @@ import ( "encoding/json" "errors" "fmt" + "io" + "strconv" "strings" "github.com/canonical/go-tpm2" @@ -303,11 +305,11 @@ type CheckResult struct { func (r CheckResult) String() string { w := new(bytes.Buffer) fmt.Fprintf(w, "\nEFI based TPM protected FDE test support results:\n") - fmt.Fprintf(w, "- Best PCR algorithm: %v\n", r.PCRAlg) - fmt.Fprintf(w, "- Secure boot CAs used for verification:\n") + io.WriteString(w, makeIndentedListItem(0, "-", fmt.Sprintf("Best PCR algorithm: %v\n", r.PCRAlg))) + io.WriteString(w, makeIndentedListItem(0, "-", fmt.Sprintf("Secure boot CAs used for verification:\n"))) for i, ca := range r.UsedSecureBootCAs { - fmt.Fprintf(w, " %d: subject=%v, SKID=%#x, pubkeyAlg=%v, issuer=%v, AKID=%#x, sigAlg=%v\n", i+1, - ca.Subject(), ca.SubjectKeyId(), ca.PublicKeyAlgorithm(), ca.Issuer(), ca.AuthorityKeyId(), ca.SignatureAlgorithm()) + io.WriteString(w, makeIndentedListItem(2, strconv.Itoa(i+1)+":", fmt.Sprintf("subject=%v, SKID=%#x, pubkeyAlg=%v, issuer=%v, AKID=%#x, sigAlg=%v\n", + ca.Subject(), ca.SubjectKeyId(), ca.PublicKeyAlgorithm(), ca.Issuer(), ca.AuthorityKeyId(), ca.SignatureAlgorithm()))) } var flags []string for i := 0; i < 64; i++ { @@ -322,11 +324,11 @@ func (r CheckResult) String() string { flags = append(flags, str) } } - fmt.Fprintf(w, "- Flags: %s\n", strings.Join(flags, ",")) + io.WriteString(w, makeIndentedListItem(0, "-", fmt.Sprintf("Flags: %s\n", strings.Join(flags, ",")))) if r.Warnings != nil && len(r.Warnings.Errs) > 0 { - fmt.Fprintf(w, "- Warnings:\n") + io.WriteString(w, makeIndentedListItem(0, "-", "Warnings:\n")) for i := 0; i < len(r.Warnings.Errs); i++ { - fmt.Fprintf(w, "%s\n", indentLines(2, "- "+r.Warnings.Errs[i].Error())) + io.WriteString(w, makeIndentedListItem(2, "-", fmt.Sprintf("%v\n", r.Warnings.Errs[i]))) } } return w.String() diff --git a/efi/preinstall/result_test.go b/efi/preinstall/result_test.go index ff87a3c9..54b9606a 100644 --- a/efi/preinstall/result_test.go +++ b/efi/preinstall/result_test.go @@ -328,7 +328,8 @@ func (s *resultSuite) TestCheckResultStringWithWarnings(c *C) { Warnings: &RunChecksErrors{ Errs: []error{ errors.New("some error 1"), - errors.New("some error 2"), + errors.New(`some error 2 +across more than one line`), }, }, } @@ -341,5 +342,6 @@ EFI based TPM protected FDE test support results: - Warnings: - some error 1 - some error 2 + across more than one line `) } From ac6f140df0fcb09d9c52788d01a487a235487652 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 4 Dec 2024 01:11:55 +0000 Subject: [PATCH 6/6] preinstall: fix a couple of doc typos --- efi/preinstall/profile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/efi/preinstall/profile.go b/efi/preinstall/profile.go index 451ab45d..87bc48ed 100644 --- a/efi/preinstall/profile.go +++ b/efi/preinstall/profile.go @@ -213,7 +213,7 @@ func (o *pcrProfileAutoSetPcrsOption) options() ([]secboot_efi.PCRProfileEnableP // don't extend anything to the TPM, breaking the root-of-trust. This is true of the Microsoft UEFI CA 2011, // and for now, we assume to be true of the 2023 UEFI CA unless Microsoft are more transparent about what is // signed under this CA). It's also assumed to be true for any unrecognized CAs. - // This can be overridden with PCRProfileOptionsTrustCAsForBootCode. + // This can be overridden with PCRProfileOptionTrustCAsForBootCode. if o.result.Flags&NoBootManagerCodeProfileSupport > 0 { return nil, fmt.Errorf("cannot create a valid secure boot configuration: one or more CAs used for secure boot "+ "verification are not trusted to authenticate boot code and the PCRProfileOptionTrustCAsForBootCode "+ @@ -235,7 +235,7 @@ func (o *pcrProfileAutoSetPcrsOption) options() ([]secboot_efi.PCRProfileEnableP // (ie, they may have signed code in the past that can defeat our security model. This is true of the Microsoft // UEFI CA 2011, and for now, we assume to be true of the 2023 UEFI CA unless Microsoft are more transparent about // what they sign under this CA). It's also assumed to be true for any unrecognized CAs. - // This can be overridden with PCRProfileOptionsTrustCAsForVARSuppliedDrivers. + // This can be overridden with PCRProfileOptionTrustCAsForVARSuppliedDrivers. includePcr2 = true if !isPcr2Supported { return nil, fmt.Errorf("cannot create a valid secure boot configuration: one or more CAs used for secure boot "+