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) +}