Skip to content

Commit

Permalink
feat: rework secureboot and PCR signing key
Browse files Browse the repository at this point in the history
Support different providers, not only static file paths.

Drop `pcr-signing-key-public.pem` file, as we generate it on the fly
now.

See siderolabs/image-factory#19

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Nov 10, 2023
1 parent 6eade3d commit f38eaaa
Show file tree
Hide file tree
Showing 22 changed files with 891 additions and 191 deletions.
92 changes: 13 additions & 79 deletions cmd/talosctl/cmd/mgmt/gen/secureboot.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package gen

import (
"context"
stdlibx509 "crypto/x509"
"encoding/pem"
"fmt"
Expand All @@ -13,14 +14,12 @@ import (
"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/cmd/talosctl/pkg/mgmt/helpers"
"github.com/siderolabs/talos/internal/pkg/secureboot/database"
"github.com/siderolabs/talos/pkg/imager/profile"
"github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
Expand Down Expand Up @@ -99,7 +98,6 @@ func checkedWrite(path string, data []byte, perm fs.FileMode) error { //nolint:u
return os.WriteFile(path, data, perm)
}

//nolint:gocyclo
func generateSigningCerts(path, prefix, commonName string, rsaBits int, outputCert, outputDER bool) error {
currentTime := time.Now()

Expand Down Expand Up @@ -129,32 +127,7 @@ func generateSigningCerts(path, prefix, commonName string, rsaBits int, outputCe
}
}

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

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

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

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

if outputDER {
if err = saveAsDER(filepath.Join(path, prefix+"-signing-public-key.der"), privKey.PublicKeyPEM); err != nil {
return err
}
}
}

return nil
return checkedWrite(filepath.Join(path, prefix+"-signing-key.pem"), signingKey.KeyPEM, 0o600)
}

func saveAsDER(file string, pem []byte) error {
Expand All @@ -169,69 +142,30 @@ 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/
//
//nolint:gocyclo
func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, signingCertificatePath string) error {
uuid, err := uuid.NewRandom()
if err != nil {
return err
in := profile.SigningKeyAndCertificate{
KeyPath: signingKeyPath,
CertPath: signingCertificatePath,
}

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

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

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

key, err := signingPEM.GetRSAKey()
if err != nil {
return err
return fmt.Errorf("failed to create signer: %w", 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())
db, err := database.Generate(enrolledPEM, signer)
if err != nil {
return err
}

signedPK, err := efi.SignEFIVariable(key, cert, "PK", db.Bytes())
if err != nil {
return err
return fmt.Errorf("failed to generate database: %w", err)
}

// output all files with sd-boot conventional names for auto-enrolment
for _, out := range []struct {
name string
data []byte
}{
{constants.SignatureKeyAsset, signedDB},
{constants.KeyExchangeKeyAsset, signedKEK},
{constants.PlatformKeyAsset, signedPK},
} {
if err = checkedWrite(filepath.Join(path, out.name), out.data, 0o600); err != nil {
for _, entry := range db {
if err = checkedWrite(filepath.Join(path, entry.Name), entry.Contents, 0o600); err != nil {
return err
}
}
Expand Down
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ require (

require (
cloud.google.com/go/compute/metadata v0.2.3
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1
github.com/BurntSushi/toml v1.3.2
github.com/aws/aws-sdk-go-v2/config v1.22.2
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.2
Expand Down Expand Up @@ -154,7 +159,10 @@ require (
github.com/0x5a17ed/itkit v0.6.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.11.1 // indirect
Expand Down Expand Up @@ -208,6 +216,7 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.1 // indirect
Expand All @@ -231,6 +240,7 @@ require (
github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,24 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.0.0 h1:jfh/0wklBNgF8+zaEEYISFZ4kviGG9aWAgUaVClDbaA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.0.0/go.mod h1:jYmTBxPYmbqUp5pCuTC58jMXVk/NxmqeYdoMbQGVUKo=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1 h1:8TkzQBrN9PWIwo7ekdd696KpC6IfTltV2/F8qKKBWik=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1/go.mod h1:aprFpXPQiTyG5Rkz6Ot5pvU6y6YKg/AKYOcLCoxN0bk=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
Expand Down Expand Up @@ -275,6 +291,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
Expand Down
62 changes: 62 additions & 0 deletions internal/pkg/secureboot/database/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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 database generates SecureBoot auto-enrollment database.
package database

import (
"crypto/sha256"

"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/talos/internal/pkg/secureboot/pesign"
"github.com/siderolabs/talos/pkg/machinery/constants"
)

// Entry is a UEFI database entry.
type Entry struct {
Name string
Contents []byte
}

// 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) {
// 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 {
return nil, err
}

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

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

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

return []Entry{
{Name: constants.SignatureKeyAsset, Contents: signedDB},
{Name: constants.KeyExchangeKeyAsset, Contents: signedKEK},
{Name: constants.PlatformKeyAsset, Contents: signedPK},
}, nil
}
11 changes: 9 additions & 2 deletions internal/pkg/secureboot/measure/internal/pcr/bank_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package pcr

import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
Expand All @@ -18,12 +19,18 @@ import (
tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2"
)

// RSAKey is the input for the CalculateBankData function.
type RSAKey interface {
crypto.Signer
PublicRSAKey() *rsa.PublicKey
}

// CalculateBankData calculates the PCR bank data for a given set of UKI file sections.
//
// This mimics the process happening happening in the TPM when the UKI is being loaded.
func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureboot.Section]string, rsaKey *rsa.PrivateKey) ([]tpm2internal.BankData, error) {
func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureboot.Section]string, rsaKey RSAKey) ([]tpm2internal.BankData, error) {
// get fingerprint of public key
pubKeyFingerprint := sha256.Sum256(x509.MarshalPKCS1PublicKey(&rsaKey.PublicKey))
pubKeyFingerprint := sha256.Sum256(x509.MarshalPKCS1PublicKey(rsaKey.PublicRSAKey()))

hashAlg, err := alg.Hash()
if err != nil {
Expand Down
11 changes: 10 additions & 1 deletion internal/pkg/secureboot/measure/internal/pcr/bank_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package pcr_test

import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"
Expand All @@ -18,6 +19,14 @@ import (
tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2"
)

type keyWrapper struct {
*rsa.PrivateKey
}

func (k keyWrapper) PublicRSAKey() *rsa.PublicKey {
return &k.PrivateKey.PublicKey
}

func TestCalculateBankData(t *testing.T) {
t.Parallel()

Expand All @@ -36,7 +45,7 @@ func TestCalculateBankData(t *testing.T) {
secureboot.Linux: "testdata/b",
secureboot.DTB: "testdata/c",
},
key)
keyWrapper{key})
require.NoError(t, err)

require.Equal(t,
Expand Down
34 changes: 6 additions & 28 deletions internal/pkg/secureboot/measure/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
package measure

import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"

"github.com/google/go-tpm/tpm2"

Expand All @@ -24,33 +21,14 @@ import (
// SectionsData holds a map of Section to file path to the corresponding section.
type SectionsData map[secureboot.Section]string

func loadRSAKey(path string) (*rsa.PrivateKey, error) {
keyData, err := os.ReadFile(path)
if err != nil {
return nil, err
}

// convert private key to rsa.PrivateKey
rsaPrivateKeyBlock, _ := pem.Decode(keyData)
if rsaPrivateKeyBlock == nil {
return nil, err
}

rsaKey, err := x509.ParsePKCS1PrivateKey(rsaPrivateKeyBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("parse private key failed: %v", err)
}

return rsaKey, nil
// RSAKey is the input for the CalculateBankData function.
type RSAKey interface {
crypto.Signer
PublicRSAKey() *rsa.PublicKey
}

// GenerateSignedPCR generates the PCR signed data for a given set of UKI file sections.
func GenerateSignedPCR(sectionsData SectionsData, rsaKeyPath string) (*tpm2internal.PCRData, error) {
rsaKey, err := loadRSAKey(rsaKeyPath)
if err != nil {
return nil, err
}

func GenerateSignedPCR(sectionsData SectionsData, rsaKey RSAKey) (*tpm2internal.PCRData, error) {
data := &tpm2internal.PCRData{}

for _, algo := range []struct {
Expand Down
Loading

0 comments on commit f38eaaa

Please sign in to comment.