Skip to content

Commit

Permalink
feat(talosctl): append microsoft secure boot certs
Browse files Browse the repository at this point in the history
This patch adds a flag to `secureboot.database.Generate` to append the
Microsoft UEFI secure boot DB and KEK certificates to the appropriate
ESLs, in addition to complimentary command line flags.

This patch also includes a copy of said Microsoft certificates. The
certificates are downloaded from an official Microsoft repo.

Signed-off-by: Jean-Francois Roy <jf@devklog.net>
Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
jfroy authored and smira committed Jul 22, 2024
1 parent fd6ddd1 commit fd54dc1
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 15 deletions.
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,16 @@ FROM scratch AS ipxe-generate
COPY --from=pkg-ipxe-amd64 /usr/libexec/snp.efi /amd64/snp.efi
COPY --from=pkg-ipxe-arm64 /usr/libexec/snp.efi /arm64/snp.efi

FROM scratch AS microsoft-secureboot-database
ADD https://github.com/microsoft/secureboot_objects.git /

FROM scratch AS microsoft-key-keys
COPY --from=microsoft-secureboot-database /PreSignedObjects/KEK/Certificates/*.der /kek/

FROM scratch AS microsoft-db-keys
COPY --from=microsoft-secureboot-database /PreSignedObjects/DB/Certificates/MicCor*.der /db/
COPY --from=microsoft-secureboot-database /PreSignedObjects/DB/Certificates/microsoft*.der /db/

FROM --platform=${BUILDPLATFORM} scratch AS generate
COPY --from=proto-format-build /src/api /api/
COPY --from=generate-build /api/common/*.pb.go /pkg/machinery/api/common/
Expand All @@ -333,6 +343,8 @@ COPY --from=go-generate /src/pkg/machinery/extensions/ /pkg/machinery/extensions
COPY --from=ipxe-generate / /pkg/provision/providers/vm/internal/ipxe/data/ipxe/
COPY --from=embed-abbrev / /
COPY --from=pkg-ca-certificates /etc/ssl/certs/ca-certificates /internal/app/machined/pkg/controllers/secrets/data/
COPY --from=microsoft-key-keys / /internal/pkg/secureboot/database/certs/
COPY --from=microsoft-db-keys / /internal/pkg/secureboot/database/certs/

# The base target provides a container that can be used to build all Talos
# assets.
Expand All @@ -345,6 +357,7 @@ COPY --from=generate /pkg/flannel/ ./pkg/flannel/
COPY --from=generate /pkg/imager/ ./pkg/imager/
COPY --from=generate /pkg/machinery/ ./pkg/machinery/
COPY --from=generate /internal/app/machined/pkg/controllers/secrets/data/ ./internal/app/machined/pkg/controllers/secrets/data/
COPY --from=generate /internal/pkg/secureboot/database/certs/ ./internal/pkg/secureboot/database/certs/
COPY --from=embed / ./
RUN --mount=type=cache,target=/.cache go list all >/dev/null
WORKDIR /src/pkg/machinery
Expand Down
11 changes: 11 additions & 0 deletions cmd/installer/cmd/imager/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ var cmdFlags struct {
OverlayName string
OverlayImage string
OverlayOptions []string
// Only used when generating a secure boot iso without also providing a secure boot database.
SecurebootIncludeWellKnownCerts bool
}

// rootCmd represents the base command when called without any subcommands.
Expand Down Expand Up @@ -173,6 +175,13 @@ var rootCmd = &cobra.Command{

prof.Output.ImageOptions.DiskSize = int64(size)
}

if cmdFlags.SecurebootIncludeWellKnownCerts {
if prof.Input.SecureBoot == nil {
prof.Input.SecureBoot = &profile.SecureBootAssets{}
}
prof.Input.SecureBoot.IncludeWellKnownCerts = true
}
}

if err := os.MkdirAll(cmdFlags.OutputPath, 0o755); err != nil {
Expand Down Expand Up @@ -229,4 +238,6 @@ func init() {
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-name")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-image")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-option")
rootCmd.PersistentFlags().BoolVar(
&cmdFlags.SecurebootIncludeWellKnownCerts, "secureboot-include-well-known-certs", false, "Include well-known (Microsoft) UEFI certificates when generating a secure boot database")
}
8 changes: 6 additions & 2 deletions cmd/talosctl/cmd/mgmt/gen/secureboot.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ var genSecurebootPCRCmd = &cobra.Command{
var genSecurebootDatabaseCmdFlags struct {
enrolledCertificatePath string
signingCertificatePath, signingKeyPath string
includeWellKnownCerts bool
}

// genSecurebootDatabaseCmd represents the `gen secureboot database` command.
Expand All @@ -78,6 +79,7 @@ var genSecurebootDatabaseCmd = &cobra.Command{
genSecurebootDatabaseCmdFlags.enrolledCertificatePath,
genSecurebootDatabaseCmdFlags.signingKeyPath,
genSecurebootDatabaseCmdFlags.signingCertificatePath,
genSecurebootDatabaseCmdFlags.includeWellKnownCerts,
)
},
}
Expand Down Expand Up @@ -140,7 +142,7 @@ func saveAsDER(file string, pem []byte) error {
// generateSecureBootDatabase generates a UEFI database to enroll the signing certificate.
//
// ref: https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, signingCertificatePath string) error {
func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, signingCertificatePath string, includeWellKnownCerts bool) error {
in := profile.SigningKeyAndCertificate{
KeyPath: signingKeyPath,
CertPath: signingCertificatePath,
Expand All @@ -156,7 +158,7 @@ func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, s
return err
}

db, err := database.Generate(enrolledPEM, signer)
db, err := database.Generate(enrolledPEM, signer, database.IncludeWellKnownCertificates(includeWellKnownCerts))
if err != nil {
return fmt.Errorf("failed to generate database: %w", err)
}
Expand Down Expand Up @@ -186,6 +188,8 @@ func init() {
&genSecurebootDatabaseCmdFlags.signingCertificatePath, "signing-certificate", helpers.ArtifactPath(constants.SecureBootSigningCertAsset), "path to the certificate used to sign the database")
genSecurebootDatabaseCmd.Flags().StringVar(
&genSecurebootDatabaseCmdFlags.signingKeyPath, "signing-key", helpers.ArtifactPath(constants.SecureBootSigningKeyAsset), "path to the key used to sign the database")
genSecurebootDatabaseCmd.Flags().BoolVar(
&genSecurebootDatabaseCmdFlags.includeWellKnownCerts, "include-well-known-uefi-certs", false, "include well-known UEFI (Microsoft) certificates in the database")
genSecurebootCmd.AddCommand(genSecurebootDatabaseCmd)
}

Expand Down
6 changes: 6 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ Talos Linux now supports adding [custom trusted roots](https://www.talos.dev/v1.
title = "Default Node Labels"
description = """\
Talos Linux on config generation now adds a label `node.kubernetes.io/exclude-from-external-load-balancers` by default for the control plane nodes.
"""

[notes.secureboot]
title = "Secure Boot"
description = """\
Talos Linux now can optionally include well-known UEFI (Microsoft) SecureBoot keys into the auto-enrollment UEFI database.
"""

[make_deps]
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
129 changes: 121 additions & 8 deletions internal/pkg/secureboot/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ package database

import (
"crypto/sha256"
"crypto/x509"
"embed"
"path/filepath"
"sync"

"github.com/foxboron/go-uefi/efi/signature"
"github.com/foxboron/go-uefi/efi/util"
Expand All @@ -23,33 +27,142 @@ type Entry struct {
Contents []byte
}

const (
microsoftSignatureOwnerGUID = "77fa9abd-0359-4d32-bd60-28f4e78f784b"
)

// Well-known UEFI DB certificates (DER data).
//
//go:embed certs/db/*.der
var wellKnownDB embed.FS

// Well-known UEFI KEK certificates (PEM data).
//
//go:embed certs/kek/*.der
var wellKnownKEK embed.FS

func loadWellKnownCertificates(fs embed.FS, path string) ([]*x509.Certificate, error) {
certs := []*x509.Certificate{}

files, err := fs.ReadDir(path)
if err != nil {
return nil, err
}

for _, file := range files {
data, err := fs.ReadFile(filepath.Join(path, file.Name()))
if err != nil {
return nil, err
}

cert, err := x509.ParseCertificate(data)
if err != nil {
return nil, err
}

certs = append(certs, cert)
}

return certs, nil
}

var wellKnownDBCertificates = sync.OnceValue(func() []*x509.Certificate {
certs, err := loadWellKnownCertificates(wellKnownDB, "certs/db")
if err != nil {
panic(err)
}

return certs
})

var wellKnownKEKCertificates = sync.OnceValue(func() []*x509.Certificate {
certs, err := loadWellKnownCertificates(wellKnownKEK, "certs/kek")
if err != nil {
panic(err)
}

return certs
})

// Options for Generate.
type Options struct {
IncludeWellKnownCertificates bool
}

// Option is a functional option for Generate.
type Option func(*Options)

// IncludeWellKnownCertificates is an option to include well-known certificates.
func IncludeWellKnownCertificates(v bool) Option {
return func(o *Options) {
o.IncludeWellKnownCertificates = v
}
}

// Generate generates a UEFI database to enroll the signing certificate.
//
// ref: https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
func Generate(enrolledCertificate []byte, signer pesign.CertificateSigner) ([]Entry, error) {
//
//nolint:gocyclo
func Generate(enrolledCertificate []byte, signer pesign.CertificateSigner, opts ...Option) ([]Entry, error) {
var options Options

for _, opt := range opts {
opt(&options)
}

// derive UUID from enrolled certificate
uuid := uuid.NewHash(sha256.New(), uuid.NameSpaceX500, enrolledCertificate, 4)

efiGUID := util.StringToGUID(uuid.String())

// Create ESL
db := signature.NewSignatureDatabase()
if err := db.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
// Create PK ESL
pk := signature.NewSignatureDatabase()
if err := pk.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
return nil, err
}

// Sign the ESL, but for each EFI variable
_, signedDB, err := signature.SignEFIVariable(efivar.Db, db, signer.Signer(), signer.Certificate())
_, signedPK, err := signature.SignEFIVariable(efivar.PK, pk, signer.Signer(), signer.Certificate())
if err != nil {
return nil, err
}

_, signedKEK, err := signature.SignEFIVariable(efivar.KEK, db, signer.Signer(), signer.Certificate())
// Create KEK ESL
kek := signature.NewSignatureDatabase()
if err := kek.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
return nil, err
}

if options.IncludeWellKnownCertificates {
owner := util.StringToGUID(microsoftSignatureOwnerGUID)
for _, cert := range wellKnownKEKCertificates() {
if err := kek.Append(signature.CERT_X509_GUID, *owner, cert.Raw); err != nil {
return nil, err
}
}
}

_, signedKEK, err := signature.SignEFIVariable(efivar.KEK, kek, signer.Signer(), signer.Certificate())
if err != nil {
return nil, err
}

_, signedPK, err := signature.SignEFIVariable(efivar.PK, db, signer.Signer(), signer.Certificate())
// Create db ESL
db := signature.NewSignatureDatabase()
if err := db.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
return nil, err
}

if options.IncludeWellKnownCertificates {
owner := util.StringToGUID(microsoftSignatureOwnerGUID)
for _, cert := range wellKnownDBCertificates() {
if err := db.Append(signature.CERT_X509_GUID, *owner, cert.Raw); err != nil {
return nil, err
}
}
}

_, signedDB, err := signature.SignEFIVariable(efivar.Db, db, signer.Signer(), signer.Certificate())
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/imager/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor

var entries []database.Entry

entries, err = database.Generate(enrolledPEM, signer)
entries, err = database.Generate(enrolledPEM, signer, database.IncludeWellKnownCertificates(i.prof.Input.SecureBoot.IncludeWellKnownCerts))
if err != nil {
return fmt.Errorf("failed to generate database: %w", err)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/imager/profile/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ type SecureBootAssets struct {
PlatformKeyPath string `yaml:"platformKeyPath,omitempty"`
KeyExchangeKeyPath string `yaml:"keyExchangeKeyPath,omitempty"`
SignatureKeyPath string `yaml:"signatureKeyPath,omitempty"`
// Optional, auto-enrollment include well-known UEFI (Microsoft) certs.
IncludeWellKnownCerts bool `yaml:"includeWellKnownCerts,omitempty"`
}

// SigningKeyAndCertificate describes a signing key & certificate.
Expand Down
9 changes: 5 additions & 4 deletions website/content/v1.8/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1535,10 +1535,11 @@ talosctl gen secureboot database [flags]
### Options

```
--enrolled-certificate string path to the certificate to enroll (default "_out/uki-signing-cert.pem")
-h, --help help for database
--signing-certificate string path to the certificate used to sign the database (default "_out/uki-signing-cert.pem")
--signing-key string path to the key used to sign the database (default "_out/uki-signing-key.pem")
--enrolled-certificate string path to the certificate to enroll (default "_out/uki-signing-cert.pem")
-h, --help help for database
--include-well-known-uefi-certs include well-known UEFI (Microsoft) certificates in the database
--signing-certificate string path to the certificate used to sign the database (default "_out/uki-signing-cert.pem")
--signing-key string path to the key used to sign the database (default "_out/uki-signing-key.pem")
```

### Options inherited from parent commands
Expand Down

0 comments on commit fd54dc1

Please sign in to comment.