Skip to content

Commit

Permalink
efi: add some unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisccoulson committed Nov 28, 2023
1 parent 6499519 commit 80feeaa
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 5 deletions.
6 changes: 6 additions & 0 deletions efi/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ var (
testUefiSigningKey1_1 []byte
testUefiCACert2 []byte
testUefiCAKey2 []byte

snakeoilCert []byte
snakeoilKey []byte
)

func initTestCertificate(key []byte, subject pkix.Name, serialNumber *big.Int, isCA bool, keyUsage x509.KeyUsage, extKeyUsage []x509.ExtKeyUsage, issuerCert []byte, issuerKey []byte) []byte {
Expand Down Expand Up @@ -125,6 +128,9 @@ func init() {
testUefiSigningKey1_1 = testutil.MustDecodePEMType("RSA PRIVATE KEY", testUefiSigningKey1_1PEM)
testUefiCAKey2 = testutil.MustDecodePEMType("RSA PRIVATE KEY", testUefiCAKey2PEM)

snakeoilCert = testutil.MustDecodePEMType("CERTIFICATE", snakeoilCertPEM)
snakeoilKey = testutil.MustDecodePEMType("RSA PRIVATE KEY", snakeoilKeyPEM)

testPKCert1 = initTestCertificate(
testPKKey1,
pkix.Name{
Expand Down
6 changes: 6 additions & 0 deletions efi/embeds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ var (

//go:embed testdata/src/certs/canonical-uefi-ca.crt
canonicalCACertPEM []byte

//go:embed testdata/src/certs/PkKek-1-snakeoil.pem
snakeoilCertPEM []byte
)

var (
//go:embed testdata/src/keys/PkKek-1-snakeoil.key
snakeoilKeyPEM []byte

//go:embed testdata/src/keys/TestPk1.key
testPKKey1PEM []byte

Expand Down
10 changes: 10 additions & 0 deletions efi/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ var (
ShimVersionIs = shimVersionIs
WithAuthority = withAuthority
WithImageRule = withImageRule
WithImageRuleOnlyForTesting = withImageRuleOnlyForTesting
WithSelfSignedSignerOnlyForTesting = withSelfSignedSignerOnlyForTesting
)

// Alias some unexported types for testing. These are required in order to pass these between functions in tests, or to access
Expand Down Expand Up @@ -185,6 +187,14 @@ func MockReadVar(fn func(string, efi.GUID) ([]byte, efi.VariableAttributes, erro
}
}

func MockSnapdenvTesting(testing bool) (restore func()) {
orig := snapdenvTesting
snapdenvTesting = func() bool { return testing }
return func() {
snapdenvTesting = orig
}
}

func NewRootVarReader(host HostEnvironment) *rootVarReader {
return &rootVarReader{
host: host,
Expand Down
42 changes: 42 additions & 0 deletions efi/image_rules_defs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
package efi_test

import (
"crypto"
"io"

efi "github.com/canonical/go-efilib"

. "gopkg.in/check.v1"
Expand Down Expand Up @@ -109,6 +112,45 @@ func (s *imageRulesDefsSuite) TestMSNewImageLoadHandlerUbuntuShim15WithFixes2(c
c.Check(shimHandler.SbatLevel, DeepEquals, ShimSbatLevel{})
}

func (s *imageRulesDefsSuite) TestMSNewImageLoadHandlerUbuntuShim15WithFixesInTesting(c *C) {
// Verify we get a correctly configured shimLoadHandler for the Ubuntu shim 15 with
// the required fixes (1.41+15+1552672080.a4a1fbe-0ubuntu1), when it is rebuilt and
// re-signed in snapd spread tests.
restore := MockSnapdenvTesting(true)
defer restore()

h := crypto.SHA256.New()
io.WriteString(h, "foo")

// simulate rebuilding and re-signing shim in spread tests
image := newMockUbuntuShimImage15a(c).unsign().withDigest(crypto.SHA256, h.Sum(nil)).sign(c, testutil.ParsePKCS1PrivateKey(c, snakeoilKey), testutil.ParseCertificate(c, snakeoilCert))

rules := MakeMicrosoftUEFICASecureBootNamespaceRules()
handler, err := rules.NewImageLoadHandler(image.newPeImageHandle())
c.Assert(err, IsNil)
c.Assert(handler, testutil.ConvertibleTo, &ShimLoadHandler{})

shimHandler := handler.(*ShimLoadHandler)
c.Check(shimHandler.Flags, Equals, ShimFlags(0))
c.Check(shimHandler.VendorDb, DeepEquals, &SecureBootDB{
Name: efi.VariableDescriptor{Name: "Shim", GUID: ShimGuid},
Contents: efi.SignatureDatabase{efitest.NewSignatureListX509(c, canonicalCACert, efi.GUID{})},
})
c.Check(shimHandler.SbatLevel, DeepEquals, ShimSbatLevel{})
}

func (s *imageRulesDefsSuite) TestMSNewImageLoadHandlerIgnoreTestAuthorityWhenNotInTestMode(c *C) {
// Verify that the snakeoil key used in snapd spread tests is ignored when not in test mode.
restore := MockSnapdenvTesting(false)
defer restore()

image := newMockUbuntuShimImage15a(c).unsign().sign(c, testutil.ParsePKCS1PrivateKey(c, snakeoilKey), testutil.ParseCertificate(c, snakeoilCert))

rules := MakeMicrosoftUEFICASecureBootNamespaceRules()
_, err := rules.NewImageLoadHandler(image.newPeImageHandle())
c.Check(err, Equals, ErrNoHandler)
}

func (s *imageRulesDefsSuite) TestMSNewImageLoadHandlerUbuntuGrubSbat(c *C) {
// Verify we get a correctly configured grubLoadHandler for the Ubuntu grub
image := newMockUbuntuGrubImage3(c)
Expand Down
15 changes: 10 additions & 5 deletions efi/secureboot_namespace_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import (
"golang.org/x/xerrors"
)

var (
snapdenvTesting = snapdenv.Testing
)

// vendorAuthorityGetter provides a way for an imageLoadHandler created by
// secureBootNamespaceRules to supplement the CA's associated with a secure
// boot namespace in the case where the associated image contains a delegated
Expand Down Expand Up @@ -60,10 +64,11 @@ func withAuthority(subject, subjectKeyId []byte, publicKeyAlgorithm x509.PublicK
}
}

// withSigner adds the specified secure boot authority to a secureBootNamespaceRules,
// only during testing.
// withSelfSignedSignerOnlyForTesting adds the specified secure boot authority to a
// secureBootNamespaceRules, only during testing. This also supports the case where the
// specified authority is the signer.
func withSelfSignedSignerOnlyForTesting(subject, subjectKeyId []byte, publicKeyAlgorithm x509.PublicKeyAlgorithm, signatureAlgorithm x509.SignatureAlgorithm) secureBootNamespaceOption {
if !snapdenv.Testing() {
if !snapdenvTesting() {
return func(_ *secureBootNamespaceRules) {}
}
return func(ns *secureBootNamespaceRules) {
Expand All @@ -84,10 +89,10 @@ func withImageRule(name string, match imagePredicate, create newImageLoadHandler
}
}

// withImageRule adds the specified rule to a secureBootNamespaceRules,
// withImageRuleOnlyForTesting adds the specified rule to a secureBootNamespaceRules,
// only during testing.
func withImageRuleOnlyForTesting(name string, match imagePredicate, create newImageLoadHandlerFn) secureBootNamespaceOption {
if !snapdenv.Testing() {
if !snapdenvTesting() {
return func(_ *secureBootNamespaceRules) {}
}
return withImageRule(name, match, create)
Expand Down
115 changes: 115 additions & 0 deletions efi/secureboot_namespace_rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,65 @@ func (s *secureBootNamespaceRulesSuite) TestRulesMatch3(c *C) {
c.Check(handler, DeepEquals, newMockLoadHandler())
}

func (s *secureBootNamespaceRulesSuite) TestRulesMatch4(c *C) {
// This tests that the test only rule successfully matches a binary
// with a single signature created by the authority associated with
// the namespace, when in testing mode.
restore := MockSnapdenvTesting(true)
defer restore()

image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig3)).newPeImageHandle()

cert := testutil.ParseCertificate(c, msUefiCACert)

rules := NewSecureBootNamespaceRules(
"test",
WithAuthority(cert.RawSubject, cert.SubjectKeyId, cert.PublicKeyAlgorithm),
WithImageRuleOnlyForTesting(
"rule1",
&mockImagePredicate{result: true},
func(i PeImageHandle) (ImageLoadHandler, error) {
c.Check(i, Equals, image)

return newMockLoadHandler(), nil
},
),
)
handler, err := rules.NewImageLoadHandler(image)
c.Check(err, IsNil)
c.Check(handler, DeepEquals, newMockLoadHandler())
}

func (s *secureBootNamespaceRulesSuite) TestRulesMatch5(c *C) {
// This tests that the rules successfully match a binary with a single
// signature signed directly by the test-only authority associated with the
// namespace, when in testing mode.
restore := MockSnapdenvTesting(true)
defer restore()

cert := testutil.ParseCertificate(c, snakeoilCert)

sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig3)
image := newMockImage().withDigest(sig.DigestAlgorithm(), sig.Digest()).sign(c, testutil.ParsePKCS1PrivateKey(c, snakeoilKey), cert).newPeImageHandle()

rules := NewSecureBootNamespaceRules(
"test",
WithSelfSignedSignerOnlyForTesting(cert.RawSubject, cert.SubjectKeyId, cert.PublicKeyAlgorithm, cert.SignatureAlgorithm),
WithImageRule(
"rule1",
&mockImagePredicate{result: true},
func(i PeImageHandle) (ImageLoadHandler, error) {
c.Check(i, Equals, image)

return newMockLoadHandler(), nil
},
),
)
handler, err := rules.NewImageLoadHandler(image)
c.Check(err, IsNil)
c.Check(handler, DeepEquals, newMockLoadHandler())
}

func (s *secureBootNamespaceRulesSuite) TestRulesNoMatch1(c *C) {
// This tests that the rules don't match a binary without a signature
// created by the authority associated with the namespace.
Expand Down Expand Up @@ -186,6 +245,62 @@ func (s *secureBootNamespaceRulesSuite) TestRulesNoMatch3(c *C) {
c.Check(cond.testedImages, IsNil)
}

func (s *secureBootNamespaceRulesSuite) TestRulesNoMatch4(c *C) {
// This tests that testing only rules don't match a binary with a single
// signature created by the authority associated with the namespace.
restore := MockSnapdenvTesting(false)
defer restore()

image := newMockImage().appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig3)).newPeImageHandle()

cert := testutil.ParseCertificate(c, msUefiCACert)

cond := &mockImagePredicate{result: true}
rules := NewSecureBootNamespaceRules(
"test",
WithAuthority(cert.RawSubject, cert.SubjectKeyId, cert.PublicKeyAlgorithm),
WithImageRuleOnlyForTesting(
"rule1",
cond,
func(i PeImageHandle) (ImageLoadHandler, error) {
return nil, errors.New("not reached")
},
),
)
_, err := rules.NewImageLoadHandler(image)
c.Check(err, Equals, ErrNoHandler)
c.Check(cond.testedImages, IsNil)
}

func (s *secureBootNamespaceRulesSuite) TestRulesNoMatch5(c *C) {
// This tests that the rules don't match a binary with a single signature
// signed directly by the test-only authority associated with the namespace,
// when not in testing mode.
restore := MockSnapdenvTesting(false)
defer restore()

cert := testutil.ParseCertificate(c, snakeoilCert)

sig := efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig3)
image := newMockImage().withDigest(sig.DigestAlgorithm(), sig.Digest()).sign(c, testutil.ParsePKCS1PrivateKey(c, snakeoilKey), cert).newPeImageHandle()

cond := &mockImagePredicate{result: true}
rules := NewSecureBootNamespaceRules(
"test",
WithSelfSignedSignerOnlyForTesting(cert.RawSubject, cert.SubjectKeyId, cert.PublicKeyAlgorithm, cert.SignatureAlgorithm),
WithImageRule(
"rule1",
cond,
func(i PeImageHandle) (ImageLoadHandler, error) {
return nil, errors.New("not reached")
},
),
)
_, err := rules.NewImageLoadHandler(image)
c.Check(err, Equals, ErrNoHandler)
c.Check(cond.testedImages, IsNil)
}

type mockLoadHandlerWithVendorAuthorities struct {
*mockLoadHandler
vendorCerts []*x509.Certificate
Expand Down
19 changes: 19 additions & 0 deletions efi/testdata/src/certs/PkKek-1-snakeoil.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUSbJC1oRCJUbGkwfWHscBeZrRHZcwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UECgwJU25ha2UgT2lsMB4XDTE5MTEwMTIyMDI1NVoXDTE5MTIw
MTIyMDI1NVowFDESMBAGA1UECgwJU25ha2UgT2lsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAzUDpJwDzDpLo2ytVRSgt/QWRYk/Yjae5fbujitq73XYL
uDZ+/Wf5U6zpOfyfzX/l5R0KCV9XYUJF47QEmNCnoWpg3cRdRry+3FIYtdnNK151
AZ2L74OI4sMX1akSE+MfZFgdPFcm+n0uJgQuvRYGyYaR6N1wbhJ/2iOOba+sbKyc
aKiL1fSjip2criHA/05cYSomdUT+rTUZALFdCQuOU+gX8Rqhmfbo8VEE7MpE3nrv
HocQAFphyYgG8jadjggymE7sQEZGrBqOrwMDHitbpoGNlOI2VdFgL5jRKHuB61iC
kqTmSWuS4lbOEJmms6hhQnTnu/yK7O3NEWegAPMrtQIDAQABo1MwUTAdBgNVHQ4E
FgQUFD7OXb2T6sOysRo3hj2f15SX8I8wHwYDVR0jBBgwFoAUFD7OXb2T6sOysRo3
hj2f15SX8I8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANZRB
NFVUVZVehpj3QGbbSjp77m0V6JrEYn6u/XjLRFsUNw5Hh35UCR0HkKZ0cLgrVKb/
8yL6LaYLOY6yDwEFWMtLXiF2S4noO8raEgW6A7DHawb2Y4ZNFRO4oBkyWbtd36Uu
UfSszs2av048wb5J/pNedRSx8I/FiCNWummzpkBHzx023TdLPd8fmkmG7ZBpStN0
Y//EE4DKTfHxAwt5w7WdZF5EY/KHPopnR+WSrdutRIK6zT+/+vKihtHYZbrv+7Ap
K7xOM/zJ6E9vUROmuOhL3YL3MuLn5qHEvhM0eMxEAlCnSJlFkQE4/RXhDpZJYbR7
x+PQllgoo4H6W30Dew==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions efi/testdata/src/keys/PkKek-1-snakeoil.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzUDpJwDzDpLo2ytVRSgt/QWRYk/Yjae5fbujitq73XYLuDZ+
/Wf5U6zpOfyfzX/l5R0KCV9XYUJF47QEmNCnoWpg3cRdRry+3FIYtdnNK151AZ2L
74OI4sMX1akSE+MfZFgdPFcm+n0uJgQuvRYGyYaR6N1wbhJ/2iOOba+sbKycaKiL
1fSjip2criHA/05cYSomdUT+rTUZALFdCQuOU+gX8Rqhmfbo8VEE7MpE3nrvHocQ
AFphyYgG8jadjggymE7sQEZGrBqOrwMDHitbpoGNlOI2VdFgL5jRKHuB61iCkqTm
SWuS4lbOEJmms6hhQnTnu/yK7O3NEWegAPMrtQIDAQABAoIBAQClG/Ux8fqTm6wD
Ok2CrzqnUgZMbmyGLwjw0rNRLHl5Qc9VpUjsMeqH3A1VHxmxppPZhU2gknho/XXZ
IOOdxiNCnp9DZgWetJBYDRZhms4HeBlpbkG2Lzo7J7MRpcqsAsUq5BLIilcJow8v
d6fdZU2aaU0QZKlfcjQ01uto4qsyUws1oph1sj5SuzI3zslf1/xg/K7oK9Bf4pLo
M1SVwiLoZzutqLlNSJqPgIG94yTOYduglnq4WvDXes+pbxzNINNeIMy+NCa8Xoaj
pGKf+lReql9+7Ex8gMPSeEVEETgvyy/l2AAEGTL9xgSYZJ+hTgZSrgZiS8NNZkyl
7jchn9RBAoGBAOnEoYe7jyR3qqh6ZnXbdAwYMBbnh96Vz79WurAwbfD9zyd++qfF
9UKaC99LGWHPnBFyXInKGquTaq5CQDZbR4NNLB6snxH2g0trK9/HualqA+NQbBXW
zZn3tkmrp15kme3f0A/NdJflGkTQkXaGXJnghaXF0bNDdZ3NgtMPmGvxAoGBAODG
DwmeN5QdankoVHGmP1hYlpyazXaCUafUu8YTwKqV5htfbrXPoHk7zfsvGKjlYNSA
ml6X2FewRu1XQ95i0EyMXWovfEZeKxASq0hlenMl5IIWhnmRy9jPfxS8Qma3xKW8
5fNpGnAiRwmokIrvSFU7Dmm8KFuvMRL389RR2hAFAoGAJj63Np+m4Y56aynrTWjj
6X6jj3u9rNrRrDoiPOUPd5fupkWN+JjYcVcGjjeZJXlzzR7qqNhZ2Kw7jdrk6n+w
iXi8uSAAvk2YWWRlPy1L4L2peS0VIdWMuZ3fQtpEfZrz3Aj73zn700X2MD/3LG77
A2nKM77TegKVYYXX23P3ueECgYBWVTxpYU/PoAzJRGhKzqP99ujCdeL85ckfQWJu
DiAuZSzgzqLjSkBE0GepVsmN9axfcpXYR1JdO+PCPYZf4GxJOpCWvG5rSLgZkFIh
mHiTybYfES/NeeZl/Cy/dh+6/FKXphpch74T29aVKx15f7a0pd4/VnnZ3UxoELyi
jF3JUQKBgQCgsHr8ia5YZ722UM48lc1j8st9NvN+9J7E2JzgYdslbT04XDgnsKfU
xxMtBQFZDGKhNbHaQDedT3Q+VDxhaEZF3f+R0E0g5FRh4Tu1aIVzPx/5zUILaLaA
0UjJMU8pkEx2neSzXrIt3QhKmtYEwoMofPXQoxdafppy4tMSE7ZKAA==
-----END RSA PRIVATE KEY-----

0 comments on commit 80feeaa

Please sign in to comment.