Skip to content

Commit

Permalink
refactor: extract secure boot certificate generation
Browse files Browse the repository at this point in the history
Fixes siderolabs#7412

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira committed Jul 3, 2023
1 parent 6be5a13 commit c9a9f95
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 164 deletions.
8 changes: 4 additions & 4 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@ local creds_env_vars = {

local external_artifacts = Step('external-artifacts', depends_on=[setup_ci]);
local generate = Step('generate', target='generate docs', depends_on=[setup_ci]);
local check_dirty = Step('check-dirty', depends_on=[generate, external_artifacts]);
local uki_certs = Step('uki-certs', depends_on=[setup_ci], environment={ PLATFORM: 'linux/amd64' });
local build = Step('build', target='talosctl-all kernel initramfs installer imager talos _out/integration-test-linux-amd64', depends_on=[check_dirty, uki_certs], environment={ IMAGE_REGISTRY: local_registry, PUSH: true });
local uki_certs = Step('uki-certs', depends_on=[generate], environment={ PLATFORM: 'linux/amd64' });
local check_dirty = Step('check-dirty', depends_on=[generate, external_artifacts, uki_certs]);
local build = Step('build', target='talosctl-all kernel initramfs installer imager talos _out/integration-test-linux-amd64', depends_on=[check_dirty], environment={ IMAGE_REGISTRY: local_registry, PUSH: true });
local lint = Step('lint', depends_on=[build]);
local talosctl_cni_bundle = Step('talosctl-cni-bundle', depends_on=[build, lint]);
local iso = Step('iso', target='iso', depends_on=[build], environment={ IMAGE_REGISTRY: local_registry });
Expand Down Expand Up @@ -377,8 +377,8 @@ local default_steps = [
setup_ci,
external_artifacts,
generate,
check_dirty,
uki_certs,
check_dirty,
build,
lint,
talosctl_cni_bundle,
Expand Down
15 changes: 9 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -712,16 +712,19 @@ COPY ./hack/ukify /go/src/github.com/siderolabs/ukify
RUN --mount=type=cache,target=/.cache \
cd /go/src/github.com/siderolabs/ukify \
&& CGO_ENABLED=1 go test ./... \
&& go build -o gen-uki-certs ./gen-certs \
&& CGO_ENABLED=1 go build -o ukify . \
&& mv gen-uki-certs /toolchain/go/bin/ \
&& mv ukify /toolchain/go/bin/

FROM --platform=${BUILDPLATFORM} ukify-tools AS gen-uki-certs
RUN gen-uki-certs
FROM base AS gen-uki-certs
ARG TARGETOS
ARG TARGETARCH
COPY --from=talosctl-targetarch /talosctl-${TARGETOS}-${TARGETARCH} /bin/talosctl
RUN /bin/talosctl gen secureboot uki
RUN /bin/talosctl gen secureboot pcr
RUN /bin/talosctl gen secureboot database

FROM scratch as uki-certs
COPY --from=gen-uki-certs /_out /
COPY --from=gen-uki-certs /src/_out /

FROM --platform=${BUILDPLATFORM} ukify-tools AS uki-build-amd64
WORKDIR /build
Expand Down Expand Up @@ -991,7 +994,7 @@ ARG GO_BUILDFLAGS
ARG GO_LDFLAGS
ARG GOAMD64
WORKDIR /src/module-sig-verify
COPY ./hack/module-sig-verify/go.mod ./hack/module-sig-verify/go.sum .
COPY ./hack/module-sig-verify/go.mod ./hack/module-sig-verify/go.sum ./
RUN --mount=type=cache,target=/.cache go mod download
COPY ./hack/module-sig-verify/main.go .
RUN --mount=type=cache,target=/.cache GOOS=linux GOARCH=amd64 GOAMD64=${GOAMD64} go build -o module-sig-verify .
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ lint: ## Runs linters on go, vulncheck, protobuf, and markdown file types.
@$(MAKE) lint-go lint-vulncheck lint-protobuf lint-markdown

check-dirty: ## Verifies that source tree is not dirty
@if test -n "`git status --porcelain`"; then echo "Source tree is dirty"; git status; exit 1 ; fi
@if test -n "`git status --porcelain`"; then echo "Source tree is dirty"; git status; git diff; exit 1 ; fi

go-mod-outdated: ## Runs the go-mod-oudated to show outdated dependencies.
@$(MAKE) target-go-mod-outdated PLATFORM=linux/amd64
Expand Down
229 changes: 229 additions & 0 deletions cmd/talosctl/cmd/mgmt/gen/secureboot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package gen

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"time"

"github.com/foxboron/go-uefi/efi"
"github.com/foxboron/go-uefi/efi/signature"
"github.com/foxboron/go-uefi/efi/util"
"github.com/google/uuid"
"github.com/siderolabs/crypto/x509"
"github.com/spf13/cobra"

"github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
)

var genSecurebootCmdFlags struct {
outputDirectory string
}

// genSecurebootCmd represents the `gen secureboot` command.
var genSecurebootCmd = &cobra.Command{
Use: "secureboot",
Short: "Generates secrets for the SecureBoot process",
Long: ``,
}

var genSecurebootUKICmdFlags struct {
commonName string
}

// genSecurebootUKICmd represents the `gen secureboot uki` command.
var genSecurebootUKICmd = &cobra.Command{
Use: "uki",
Short: "Generates a certificate which is used to sign boot assets (UKI)",
Long: ``,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return generateSigningCerts(genSecurebootCmdFlags.outputDirectory, "uki", genSecurebootUKICmdFlags.commonName, 4096)
},
}

var genSecurebootPCRCmdFlags struct {
commonName string
}

// genSecurebootPCRCmd represents the `gen secureboot pcr` command.
var genSecurebootPCRCmd = &cobra.Command{
Use: "pcr",
Short: "Generates a certificate which is used to sign TPM PCR values",
Long: ``,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return generateSigningCerts(genSecurebootCmdFlags.outputDirectory, "pcr", genSecurebootPCRCmdFlags.commonName, 2048)
},
}

var genSecurebootDatabaseCmdFlags struct {
enrolledCertificatePath string
signingCertificatePath, signingKeyPath string
}

// genSecurebootDatabaseCmd represents the `gen secureboot database` command.
var genSecurebootDatabaseCmd = &cobra.Command{
Use: "database",
Short: "Generates a UEFI database to enroll the signing certificate",
Long: ``,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return generateSecureBootDatabase(
genSecurebootCmdFlags.outputDirectory,
genSecurebootDatabaseCmdFlags.enrolledCertificatePath,
genSecurebootDatabaseCmdFlags.signingKeyPath,
genSecurebootDatabaseCmdFlags.signingCertificatePath,
)
},
}

func checkedWrite(path string, data []byte, perm fs.FileMode) error { //nolint:unparam
if err := validateFileExists(path); err != nil {
return err
}

if dirname := filepath.Dir(path); dirname != "." {
if err := os.MkdirAll(dirname, 0o700); err != nil {
return err
}
}

fmt.Fprintf(os.Stderr, "writing %s\n", path)

return os.WriteFile(path, data, perm)
}

func generateSigningCerts(path, prefix, commonName string, rsaBits int) error {
currentTime := time.Now()

opts := []x509.Option{
x509.RSA(true),
x509.Bits(rsaBits),
x509.CommonName(commonName),
x509.NotAfter(currentTime.Add(secrets.CAValidityTime)),
x509.NotBefore(currentTime),
x509.Organization(commonName),
}

signingKey, err := x509.NewSelfSignedCertificateAuthority(opts...)
if err != nil {
return err
}

if err = checkedWrite(filepath.Join(path, prefix+"-signing-cert.pem"), signingKey.CrtPEM, 0o600); err != nil {
return err
}

if err = checkedWrite(filepath.Join(path, prefix+"-signing-key.pem"), signingKey.KeyPEM, 0o600); err != nil {
return err
}

pemKey := x509.PEMEncodedKey{
Key: signingKey.KeyPEM,
}

privKey, err := pemKey.GetRSAKey()
if err != nil {
return err
}

return checkedWrite(filepath.Join(path, prefix+"-signing-public-key.pem"), privKey.PublicKeyPEM, 0o600)
}

// generateSecureBootDatabase generates a UEFI database to enroll the signing certificate.
//
// ref: https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
//
//nolint:gocyclo
func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, signingCertificatePath string) error {
uuid, err := uuid.NewRandom()
if err != nil {
return err
}

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

// Reuse the generated test signing key for secure boot
signingPEM, err := x509.NewCertificateAndKeyFromFiles(signingCertificatePath, signingKeyPath)
if err != nil {
return err
}

cert, err := signingPEM.GetCert()
if err != nil {
return err
}

key, err := signingPEM.GetRSAKey()
if err != nil {
return err
}

enrolledPEM, err := os.ReadFile(enrolledCertificatePath)
if err != nil {
return err
}

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

// Sign the ESL, but for each EFI variable
signedDB, err := efi.SignEFIVariable(key, cert, "db", db.Bytes())
if err != nil {
return err
}

signedKEK, err := efi.SignEFIVariable(key, cert, "KEK", db.Bytes())
if err != nil {
return err
}

signedPK, err := efi.SignEFIVariable(key, cert, "PK", db.Bytes())
if err != nil {
return err
}

// output all files with sd-boot conventional names for auto-enrolment
for _, out := range []struct {
name string
data []byte
}{
{"db.auth", signedDB},
{"KEK.auth", signedKEK},
{"PK.auth", signedPK},
} {
if err = checkedWrite(filepath.Join(path, out.name), out.data, 0o600); err != nil {
return err
}
}

return nil
}

func init() {
genSecurebootCmd.PersistentFlags().StringVarP(&genSecurebootCmdFlags.outputDirectory, "output", "o", "_out", "path to the directory storing the generated files")
Cmd.AddCommand(genSecurebootCmd)

genSecurebootUKICmd.Flags().StringVar(&genSecurebootUKICmdFlags.commonName, "common-name", "Test UKI Signing Key", "common name for the certificate")
genSecurebootCmd.AddCommand(genSecurebootUKICmd)

genSecurebootPCRCmd.Flags().StringVar(&genSecurebootPCRCmdFlags.commonName, "common-name", "Test PCR Signing Key", "common name for the certificate")
genSecurebootCmd.AddCommand(genSecurebootPCRCmd)

genSecurebootDatabaseCmd.Flags().StringVar(
&genSecurebootDatabaseCmdFlags.enrolledCertificatePath, "enrolled-certificate", "_out/uki-signing-cert.pem", "path to the certificate to enroll")
genSecurebootDatabaseCmd.Flags().StringVar(
&genSecurebootDatabaseCmdFlags.signingCertificatePath, "signing-certificate", "_out/uki-signing-cert.pem", "path to the certificate used to sign the database")
genSecurebootDatabaseCmd.Flags().StringVar(
&genSecurebootDatabaseCmdFlags.signingKeyPath, "signing-key", "_out/uki-signing-key.pem", "path to the key used to sign the database")
genSecurebootCmd.AddCommand(genSecurebootDatabaseCmd)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/ecks/uefi v0.0.0-20221116212947-caef65d070eb
github.com/emicklei/dot v1.5.0
github.com/fatih/color v1.15.0
github.com/foxboron/go-uefi v0.0.0-20230701115042-32187aa193d0
github.com/freddierice/go-losetup/v2 v2.0.1
github.com/fsnotify/fsnotify v1.6.0
github.com/gdamore/tcell/v2 v2.6.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/foxboron/go-uefi v0.0.0-20230701115042-32187aa193d0 h1:3KuJ4fdVZpKPQwkkQP4oOiSUXFj9bD7NibX/VXnnkn0=
github.com/foxboron/go-uefi v0.0.0-20230701115042-32187aa193d0/go.mod h1:VdozURTQHi5Rs54l+4Szi3yIJQDMfXXYrRLAjKKowWI=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/freddierice/go-losetup/v2 v2.0.1 h1:wPDx/Elu9nDV8y/CvIbEDz5Xi5Zo80y4h7MKbi3XaAI=
Expand Down Expand Up @@ -1333,6 +1335,7 @@ go.etcd.io/etcd/raft/v3 v3.5.9 h1:ZZ1GIHoUlHsn0QVqiRysAm3/81Xx7+i2d7nSdWxlOiI=
go.etcd.io/etcd/raft/v3 v3.5.9/go.mod h1:WnFkqzFdZua4LVlVXQEGhmooLeyS7mqzS4Pf4BCVqXg=
go.etcd.io/etcd/server/v3 v3.5.9 h1:vomEmmxeztLtS5OEH7d0hBAg4cjVIu9wXuNzUZx2ZA0=
go.etcd.io/etcd/server/v3 v3.5.9/go.mod h1:GgI1fQClQCFIzuVjlvdbMxNbnISt90gdfYyqiAIt65g=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
Expand Down
Loading

0 comments on commit c9a9f95

Please sign in to comment.