diff --git a/Dockerfile b/Dockerfile index 650eee11b6..c16170ef57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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/ @@ -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. @@ -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 diff --git a/cmd/installer/cmd/imager/root.go b/cmd/installer/cmd/imager/root.go index fea064f637..51a48e502b 100644 --- a/cmd/installer/cmd/imager/root.go +++ b/cmd/installer/cmd/imager/root.go @@ -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. @@ -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 { @@ -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") } diff --git a/cmd/talosctl/cmd/mgmt/gen/secureboot.go b/cmd/talosctl/cmd/mgmt/gen/secureboot.go index 03959691e3..f26652b232 100644 --- a/cmd/talosctl/cmd/mgmt/gen/secureboot.go +++ b/cmd/talosctl/cmd/mgmt/gen/secureboot.go @@ -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. @@ -78,6 +79,7 @@ var genSecurebootDatabaseCmd = &cobra.Command{ genSecurebootDatabaseCmdFlags.enrolledCertificatePath, genSecurebootDatabaseCmdFlags.signingKeyPath, genSecurebootDatabaseCmdFlags.signingCertificatePath, + genSecurebootDatabaseCmdFlags.includeWellKnownCerts, ) }, } @@ -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, @@ -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) } @@ -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) } diff --git a/hack/release.toml b/hack/release.toml index 8f10fc666f..85b313b3bc 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -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] diff --git a/internal/pkg/secureboot/database/certs/db/MicCorUEFCA2011_2011-06-27.der b/internal/pkg/secureboot/database/certs/db/MicCorUEFCA2011_2011-06-27.der new file mode 100644 index 0000000000..9aa6ac6c79 Binary files /dev/null and b/internal/pkg/secureboot/database/certs/db/MicCorUEFCA2011_2011-06-27.der differ diff --git a/internal/pkg/secureboot/database/certs/db/microsoft uefi ca 2023.der b/internal/pkg/secureboot/database/certs/db/microsoft uefi ca 2023.der new file mode 100644 index 0000000000..39a91b3173 Binary files /dev/null and b/internal/pkg/secureboot/database/certs/db/microsoft uefi ca 2023.der differ diff --git a/internal/pkg/secureboot/database/certs/kek/MicCorKEKCA2011_2011-06-24.der b/internal/pkg/secureboot/database/certs/kek/MicCorKEKCA2011_2011-06-24.der new file mode 100644 index 0000000000..2787083e0c Binary files /dev/null and b/internal/pkg/secureboot/database/certs/kek/MicCorKEKCA2011_2011-06-24.der differ diff --git a/internal/pkg/secureboot/database/certs/kek/microsoft corporation kek 2k ca 2023.der b/internal/pkg/secureboot/database/certs/kek/microsoft corporation kek 2k ca 2023.der new file mode 100644 index 0000000000..e6ffb4f975 Binary files /dev/null and b/internal/pkg/secureboot/database/certs/kek/microsoft corporation kek 2k ca 2023.der differ diff --git a/internal/pkg/secureboot/database/database.go b/internal/pkg/secureboot/database/database.go index c2264d67c1..4b536282e4 100644 --- a/internal/pkg/secureboot/database/database.go +++ b/internal/pkg/secureboot/database/database.go @@ -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" @@ -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 } diff --git a/pkg/imager/out.go b/pkg/imager/out.go index d81ef934dd..14338ef4d6 100644 --- a/pkg/imager/out.go +++ b/pkg/imager/out.go @@ -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) } diff --git a/pkg/imager/profile/input.go b/pkg/imager/profile/input.go index bd46ee2853..c73391b913 100644 --- a/pkg/imager/profile/input.go +++ b/pkg/imager/profile/input.go @@ -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. diff --git a/website/content/v1.8/reference/cli.md b/website/content/v1.8/reference/cli.md index e90520da73..e7f363e09d 100644 --- a/website/content/v1.8/reference/cli.md +++ b/website/content/v1.8/reference/cli.md @@ -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