From 282ccb2b0532d6f495a938b6fd8c0d2c9cd355da Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Wed, 1 Apr 2026 16:05:04 +0200 Subject: [PATCH] efi: add CheckImageSignatureIsValidForHost function The new function allows a update manager to look if the given image is signed with keys that are allowed by the host secure boot stack. This way an update manager can detect that an asset signature is unlikely to pass verification by the boot firmware before writing or updating the asset in persistent storage. Signed-off-by: Zygmunt Krynicki --- efi/export_test.go | 8 + efi/image_trust.go | 296 +++++++++++++++++++++++++++++ efi/image_trust_test.go | 403 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 707 insertions(+) create mode 100644 efi/image_trust.go create mode 100644 efi/image_trust_test.go diff --git a/efi/export_test.go b/efi/export_test.go index 73c4ff71..3e268f34 100644 --- a/efi/export_test.go +++ b/efi/export_test.go @@ -186,6 +186,14 @@ func MockOpenPeImage(fn func(Image) (peImageHandle, error)) (restore func()) { } } +func MockCheckImageSignatureIsValidForHost(fn func(context.Context, Image) error) (restore func()) { + orig := mockedCheckImageSignatureIsValidForHost + mockedCheckImageSignatureIsValidForHost = fn + return func() { + mockedCheckImageSignatureIsValidForHost = orig + } +} + func MockSnapdenvTesting(testing bool) (restore func()) { orig := snapdenvTesting snapdenvTesting = func() bool { return testing } diff --git a/efi/image_trust.go b/efi/image_trust.go new file mode 100644 index 00000000..afdb494b --- /dev/null +++ b/efi/image_trust.go @@ -0,0 +1,296 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2026 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package efi + +import ( + "bytes" + "context" + "crypto" + "crypto/x509" + "errors" + + efi "github.com/canonical/go-efilib" + "golang.org/x/xerrors" +) + +var mockedCheckImageSignatureIsValidForHost func(ctx context.Context, image Image) error + +// CheckImageSignatureIsValidForHost checks whether the supplied image has at +// least one Authenticode signature that is authorized by the host's authorized +// signature database (the UEFI "db" variable). +// +// The image must have at least one Authenticode signature. It is not possible +// to authorize an unsigned image based solely on its digest being present in db. +// +// The image is authorized if: +// - At least one of its Authenticode signatures chains to an X.509 certificate +// authority that is enrolled in db, OR +// - The PE image digest matches a digest entry in db, where the PE image digest +// is computed for the hash algorithm implied by the EFI_SIGNATURE_LIST type +// (e.g., CertSHA256Guid implies SHA256, CertSHA384Guid implies SHA384). +// +// For digest-based authorization, the function computes the PE image digest for +// each digest algorithm present in db and checks for a match against the +// corresponding signature list entries. This allows an image to be verified +// against digest entries regardless of the hash algorithm used by its +// Authenticode signature. +// +// This is intended to be used during boot asset updates to verify that a new +// image will actually be loadable, when secure boot is enforced, by the host's +// firmware before it is installed. +// +// For example, a shim binary signed only by a newer Microsoft UEFI CA will not +// be loadable on older hardware whose db only contains the older CA. Similarly, +// an image whose digest is not in db (if digest-based authorization is used) will +// not be loadable. An unsigned image cannot be loadable even if its digest is +// enrolled in db. +// +// The context must provide access to the EFI variable backend via go-efilib's +// context mechanism. In general, pass the result of +// [HostEnvironment.VarContext] or [efi.DefaultVarContext]. +// +// Possible error conditions: +// - The image cannot be opened or is not a valid PE binary. +// - The image has no secure boot signatures. +// - The host's db variable cannot be read (eg, if EFI variables are unavailable). +// - The host's dbx variable cannot be read (eg, if EFI variables are unavailable). +// - The image is revoked by a certificate or digest entry in the host's dbx. +// - No signature on the image is authorized by the host's db. +func CheckImageSignatureIsValidForHost(ctx context.Context, image Image) error { + if mockedCheckImageSignatureIsValidForHost != nil { + return mockedCheckImageSignatureIsValidForHost(ctx, image) + } + + // Extract signatures from the image, and check for the presence of at + // least one signature before doing any further work, to give a more + // specific error if the image is not signed at all. + pei, err := openPeImage(image) + if err != nil { + return xerrors.Errorf("cannot open image: %w", err) + } + + defer pei.Close() + + sigs, err := pei.SecureBootSignatures() + if err != nil { + return xerrors.Errorf("cannot obtain secure boot signatures for image: %w", err) + } + + if len(sigs) == 0 { + return errors.New("image has no secure boot signatures") + } + + // Check for forbidden signatures in DBX first, to give a more specific + // error if the image is actually signed by a trusted CA but is revoked + // by DBX. + if err := checkDbxRevocation(ctx, pei, sigs); err != nil { + return err + } + + // Check for a trusted signature in DB. + return checkDbAuthorization(ctx, pei, sigs) +} + +func checkDbxRevocation(ctx context.Context, pei peImageHandle, sigs []*efi.WinCertificateAuthenticode) error { + dbx, err := efi.ReadSignatureDatabaseVariable(ctx, Dbx) + if err != nil { + if !errors.Is(err, efi.ErrVarNotExist) { + return xerrors.Errorf("cannot read forbidden signature database: %w", err) + } + + // If DBX doesn't exist, then there are no forbidden signatures, so we can + // just treat it as empty and allow the image to be authorized by DB. + return nil + } + + digests := newImageDigestCache(pei) + revokedByDigest, err := imageDigestIsForbiddenByDbx(digests, dbx) + if err != nil { + return err + } + + if revokedByDigest { + return errors.New("secure boot signature is forbidden by the current host's signature databases") + } + + // Per UEFI secure boot semantics, any match in DBX is sufficient to + // revoke an image, even if it has additional valid signatures. + for _, sig := range sigs { + if certIsForbiddenByDbx(sig, dbx) { + return errors.New("secure boot signature is forbidden by the current host's signature databases") + } + } + + return nil +} + +var errNoTrustedSignature = errors.New("cannot find any secure boot signature that is trusted by the current host's authorized signature database") + +func checkDbAuthorization(ctx context.Context, pei peImageHandle, imageSigs []*efi.WinCertificateAuthenticode) error { + db, err := efi.ReadSignatureDatabaseVariable(ctx, Db) + if err != nil { + if !errors.Is(err, efi.ErrVarNotExist) { + return xerrors.Errorf("cannot read authorized signature database: %w", err) + } + // If DB doesn't exist, then there are no authorized signatures, so the image cannot be + // authorized. + return errNoTrustedSignature + } + + digests := newImageDigestCache(pei) + + for _, sigList := range db { + // Check for X.509 certificate-based authorization + if sigList.Type == efi.CertX509Guid { + for _, sigEntry := range sigList.Signatures { + cert, err := x509.ParseCertificate(sigEntry.Data) + if err != nil { + continue + } + + for _, imageSig := range imageSigs { + if !imageSig.CertWithIDLikelyTrustAnchor(efi.NewX509CertIDFromCertificate(cert)) { + continue + } + + // If the signature chains to a trusted certificate, then the image is authorized. + return nil + } + } + } else { + // Skip unrecognized signature list types since we cannot use them for verification + alg := efiSignatureListTypeToDigestAlg(sigList.Type) + if alg == crypto.Hash(0) { + continue + } + + // Check for digest-based authorization + match, err := imageDigestMatchesDbSignatureList(digests, sigList) + if err != nil { + return err + } + + if match { + return nil + } + } + } + + return errNoTrustedSignature +} + +func certIsForbiddenByDbx(sig *efi.WinCertificateAuthenticode, dbx efi.SignatureDatabase) bool { + for _, sigList := range dbx { + if sigList.Type != efi.CertX509Guid { + continue + } + + for _, sigEntry := range sigList.Signatures { + revokedCert, err := x509.ParseCertificate(sigEntry.Data) + if err != nil { + continue + } + + if sig.CertWithIDLikelyTrustAnchor(efi.NewX509CertIDFromCertificate(revokedCert)) { + return true + } + } + } + + return false +} + +type imageDigestCache struct { + pei peImageHandle + digests map[crypto.Hash][]byte +} + +func newImageDigestCache(pei peImageHandle) *imageDigestCache { + return &imageDigestCache{ + pei: pei, + digests: make(map[crypto.Hash][]byte), + } +} + +func (c *imageDigestCache) digestForAlg(alg crypto.Hash) ([]byte, error) { + if digest, exists := c.digests[alg]; exists { + return digest, nil + } + + digest, err := c.pei.ImageDigest(alg) + if err != nil { + return nil, xerrors.Errorf("cannot compute image digest with %v: %w", alg, err) + } + + c.digests[alg] = digest + return digest, nil +} + +func imageDigestMatchesDbSignatureList(digests *imageDigestCache, sigList *efi.SignatureList) (bool, error) { + alg := efiSignatureListTypeToDigestAlg(sigList.Type) + if alg == crypto.Hash(0) { + return false, nil + } + + digest, err := digests.digestForAlg(alg) + if err != nil { + return false, err + } + + for _, sigEntry := range sigList.Signatures { + if bytes.Equal(sigEntry.Data, digest) { + return true, nil + } + } + + return false, nil +} + +func imageDigestIsForbiddenByDbx(digests *imageDigestCache, dbx efi.SignatureDatabase) (bool, error) { + for _, sigList := range dbx { + match, err := imageDigestMatchesDbSignatureList(digests, sigList) + if err != nil { + return false, err + } + + if match { + return true, nil + } + } + + return false, nil +} + +func efiSignatureListTypeToDigestAlg(guid efi.GUID) crypto.Hash { + switch guid { + case efi.CertSHA1Guid: + return crypto.SHA1 + case efi.CertSHA224Guid: + return crypto.SHA224 + case efi.CertSHA256Guid: + return crypto.SHA256 + case efi.CertSHA384Guid: + return crypto.SHA384 + case efi.CertSHA512Guid: + return crypto.SHA512 + default: + return crypto.Hash(0) + } +} diff --git a/efi/image_trust_test.go b/efi/image_trust_test.go new file mode 100644 index 00000000..f635594e --- /dev/null +++ b/efi/image_trust_test.go @@ -0,0 +1,403 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2026 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package efi_test + +import ( + "context" + "crypto" + "errors" + + efi "github.com/canonical/go-efilib" + . "gopkg.in/check.v1" + + . "github.com/snapcore/secboot/efi" + "github.com/snapcore/secboot/internal/efitest" +) + +type imageTrustSuite struct { + mockImageHandleMixin +} + +var _ = Suite(&imageTrustSuite{}) + +func (s *imageTrustSuite) SetUpTest(c *C) { + s.mockImageHandleMixin.SetUpTest(c) +} + +func (s *imageTrustSuite) TearDownTest(c *C) { + s.mockImageHandleMixin.TearDownTest(c) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostGood(c *C) { + // Image signed by MS UEFI CA, db contains MS UEFI CA - should succeed. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostMultipleCAsInDb(c *C) { + // Image signed by MS UEFI CA, db contains both MS PCA and MS UEFI CA - should succeed. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msPCACert, msOwnerGuid), + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostMultipleX509EntriesInSingleSignatureList(c *C) { + // Image signed by MS UEFI CA, db contains one EFI_SIGNATURE_LIST with + // multiple X.509 signature entries where only one entry matches. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + invalidCert := make([]byte, len(msUefiCACert)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + &efi.SignatureList{ + Type: efi.CertX509Guid, + Signatures: []*efi.SignatureData{ + {Owner: msOwnerGuid, Data: invalidCert}, + {Owner: msOwnerGuid, Data: msUefiCACert}, + }, + }, + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostWrongCA(c *C) { + // Image signed by MS UEFI CA, but db only has Canonical CA - should fail. + // This simulates the scenario where old hardware has a different CA. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, canonicalCACert, efi.GUID{}), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `cannot find any secure boot signature that is trusted by the current host's authorized signature database`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostUnsignedImage(c *C) { + // Unsigned image - should fail. + image := newMockImage() + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `image has no secure boot signatures`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostNoDb(c *C) { + // Image is signed but no db variable exists - treat as empty DB and fail + // with no matching trust anchor. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + vars := efitest.MakeMockVars() + // Don't set db + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `cannot find any secure boot signature that is trusted by the current host's authorized signature database`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostNoVarsBackend(c *C) { + // No EFI variables backend at all - should fail. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + env := efitest.NewMockHostEnvironment(nil, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `cannot read forbidden signature database:.*`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostCanonicalCA(c *C) { + // Image signed by Canonical CA, db contains Canonical CA - should succeed. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, grubUbuntuSig3)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, canonicalCACert, efi.GUID{}), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostBadImage(c *C) { + // Mock openPeImage to fail to test error handling. + restoreOpen := MockOpenPeImage(func(image Image) (PeImageHandle, error) { + return nil, errors.New("mock open error") + }) + defer restoreOpen() + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + image := newMockImage() + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `cannot open image: mock open error`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostNoDbx(c *C) { + // Image is signed and db exists but no dbx variable exists - treat as empty + // DBX and succeed. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostRevokedByDbxCert(c *C) { + // Image signed by MS UEFI CA and db trusts that CA, but dbx revokes it. + image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4)) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `secure boot signature is forbidden by the current host's signature databases`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostRevokedByDbxDigest(c *C) { + // Image signed by MS UEFI CA and db trusts it, but dbx revokes the image digest. + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + image := newMockImage().appendSignatures(sig) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, sig.Digest()), + }) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `secure boot signature is forbidden by the current host's signature databases`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostDigestAuthorized(c *C) { + // Image signed and its digest is in db (not via CA chain); + // should succeed. + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + image := newMockImage().appendSignatures(sig) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, sig.Digest()), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostDigestAuthorizedWithSha384DbEntry(c *C) { + // Signatures are SHA256 Authenticode, but DB authorization should use the + // image digest for the signature list algorithm (SHA384 here). + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + sha384Digest := make([]byte, crypto.SHA384.Size()) + for i := range sha384Digest { + sha384Digest[i] = 0x3c + } + image := newMockImage().withDigest(crypto.SHA384, sha384Digest).appendSignatures(sig) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, crypto.SHA384, msOwnerGuid, sha384Digest), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostDigestNotAuthorized(c *C) { + // Image signed but its digest is not in db - should fail. + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + image := newMockImage().appendSignatures(sig) + + vars := efitest.MakeMockVars() + // Set db with a wrong digest + wrongDigest := make([]byte, len(sig.Digest())) + for i := range wrongDigest { + wrongDigest[i] = 0xFF + } + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, wrongDigest), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `cannot find any secure boot signature that is trusted by the current host's authorized signature database`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostMixedCaAndDigest(c *C) { + // db has both CA entries and digest entries; image matches neither CA (db + // has wrong CA) but does match digest - should succeed. + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + image := newMockImage().appendSignatures(sig) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, canonicalCACert, efi.GUID{}), + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, sig.Digest()), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostDigestRevokedByDbx(c *C) { + // Image digest is in db but the same digest is also in dbx - dbx revocation + // should take precedence, so should fail. + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + image := newMockImage().appendSignatures(sig) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, sig.Digest()), + }) + vars.SetDbx(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, sig.Digest()), + }) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `secure boot signature is forbidden by the current host's signature databases`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostDigestRevokedByDbxWithSha384Entry(c *C) { + // Signatures are SHA256 Authenticode, but DBX revocation should use the + // image digest for the signature list algorithm (SHA384 here). + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + sha384Digest := make([]byte, crypto.SHA384.Size()) + for i := range sha384Digest { + sha384Digest[i] = 0x4d + } + image := newMockImage().withDigest(crypto.SHA384, sha384Digest).appendSignatures(sig) + + vars := efitest.MakeMockVars() + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListX509(c, msUefiCACert, msOwnerGuid), + }) + vars.SetDbx(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, crypto.SHA384, msOwnerGuid, sha384Digest), + }) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, ErrorMatches, `secure boot signature is forbidden by the current host's signature databases`) +} + +func (s *imageTrustSuite) TestCheckImageSignatureIsValidForHostMultipleDigestsInDb(c *C) { + // db has multiple digest entries whereas only one matches the image - + // should succeed. + sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4) + image := newMockImage().appendSignatures(sig) + + vars := efitest.MakeMockVars() + wrongDigest1 := make([]byte, len(sig.Digest())) + for i := range wrongDigest1 { + wrongDigest1[i] = 0xAA + } + wrongDigest2 := make([]byte, len(sig.Digest())) + for i := range wrongDigest2 { + wrongDigest2[i] = 0xBB + } + vars.SetDb(c, efi.SignatureDatabase{ + efitest.NewSignatureListDigests(c, sig.DigestAlgorithm(), msOwnerGuid, + wrongDigest1, sig.Digest(), wrongDigest2), + }) + vars.SetDbx(c, efi.SignatureDatabase{}) + env := efitest.NewMockHostEnvironment(vars, nil) + ctx := env.VarContext(context.Background()) + + err := CheckImageSignatureIsValidForHost(ctx, image) + c.Check(err, IsNil) +}