Skip to content

Commit

Permalink
Implement support for TPM key files
Browse files Browse the repository at this point in the history
Support TPM TSS2 key files for signing secure boot things

Signed-off-by: Morten Linderud <[email protected]>
  • Loading branch information
Foxboron committed Jul 30, 2024
1 parent 98466dc commit 4f3cb5f
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 16 deletions.
8 changes: 8 additions & 0 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ func createKey(state *config.State, backend string, hier hierarchy.Hierarchy, de
switch backend {
case "file", "":
return NewFileKey(hier, desc)
case "tpm":
return NewTPMKey(state.TPM, desc)
default:
return NewFileKey(hier, desc)
}
Expand Down Expand Up @@ -256,6 +258,8 @@ func readKey(state *config.State, keydir string, kc *config.KeyConfig, hier hier
switch t {
case FileBackend:
return FileKeyFromBytes(keyb, pemb)
case TPMBackend:
return TPMKeyFromBytes(state.TPM, keyb, pemb)
default:
return nil, fmt.Errorf("unknown key")
}
Expand Down Expand Up @@ -301,6 +305,8 @@ func GetBackendType(b []byte) (BackendType, error) {
switch block.Type {
case "PRIVATE KEY":
return FileBackend, nil
case "TSS2 PRIVATE KEY":
return TPMBackend, nil
default:
return "", fmt.Errorf("unknown file type: %s", block.Type)
}
Expand All @@ -319,6 +325,8 @@ func InitBackendFromKeys(state *config.State, priv, pem []byte, hier hierarchy.H
switch t {
case "file":
return FileKeyFromBytes(priv, pem)
case "tpm":
return TPMKeyFromBytes(state.TPM, priv, pem)
default:
return nil, fmt.Errorf("unknown key backend: %s", t)
}
Expand Down
144 changes: 144 additions & 0 deletions backend/tpm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package backend

import (
"bytes"
"crypto"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"path/filepath"
"time"

keyfile "github.com/foxboron/go-tpm-keyfiles"
"github.com/foxboron/sbctl/fs"
"github.com/foxboron/sbctl/hierarchy"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport"
"github.com/spf13/afero"
)

type TPMKey struct {
*keyfile.TPMKey
keytype BackendType
cert *x509.Certificate
tpm func() transport.TPMCloser
}

func NewTPMKey(tpmcb func() transport.TPMCloser, desc string) (*TPMKey, error) {
rwc := tpmcb()
key, err := keyfile.NewLoadableKey(rwc, tpm2.TPMAlgRSA, 2048, []byte(nil),
keyfile.WithDescription(desc),
)
if err != nil {
return nil, err
}

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
c := x509.Certificate{
SerialNumber: serialNumber,
PublicKeyAlgorithm: x509.RSA,
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(5, 0, 0),
Subject: pkix.Name{
Country: []string{desc},
CommonName: desc,
},
}

pubkey, err := key.PublicKey()
if err != nil {
return nil, err
}

signer, err := key.Signer(rwc, []byte(nil), []byte(nil))
if err != nil {
return nil, err
}

derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, pubkey, signer)
if err != nil {
return nil, err
}

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

return &TPMKey{
TPMKey: key,
cert: cert,
tpm: tpmcb,
}, nil
}

func (t *TPMKey) Type() BackendType { return t.keytype }
func (t *TPMKey) Certificate() *x509.Certificate { return t.cert }
func (t *TPMKey) Description() string { return t.TPMKey.Description }

func (t *TPMKey) Signer() crypto.Signer {
s, err := t.TPMKey.Signer(t.tpm(), []byte(nil), []byte(nil))
if err != nil {
panic(err)
}
return s
}

func (t *TPMKey) PrivateKeyBytes() []byte {
return t.TPMKey.Bytes()
}

func (t *TPMKey) CertificateBytes() []byte {
b := new(bytes.Buffer)
if err := pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: t.cert.Raw}); err != nil {
panic("failed producing PEM encoded certificate")
}
return b.Bytes()
}

func ReadTPMKey(vfs afero.Fs, tpmcb func() transport.TPMCloser, dir string, hier hierarchy.Hierarchy) (*TPMKey, error) {
path := filepath.Join(dir, hier.String())
keyname := filepath.Join(path, fmt.Sprintf("%s.key", hier.String()))
certname := filepath.Join(path, fmt.Sprintf("%s.pem", hier.String()))

// Read privatekey
keyb, err := fs.ReadFile(vfs, keyname)
if err != nil {
return nil, err
}

// Read certificate
pemb, err := fs.ReadFile(vfs, certname)
if err != nil {
return nil, err
}
return TPMKeyFromBytes(tpmcb, keyb, pemb)
}

func TPMKeyFromBytes(tpmcb func() transport.TPMCloser, keyb, pemb []byte) (*TPMKey, error) {
tpmkey, err := keyfile.Decode(keyb)
if err != nil {
return nil, fmt.Errorf("failed parking tpm keyfile: %v", err)
}

block, _ := pem.Decode(pemb)
if block == nil {
return nil, fmt.Errorf("no pem block")
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse cert: %w", err)
}
return &TPMKey{
TPMKey: tpmkey,
keytype: TPMBackend,
cert: cert,
tpm: tpmcb,
}, nil
}
27 changes: 22 additions & 5 deletions cmd/sbctl/create-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
exportPath string
databasePath string
Keytype string
PKKeyType, KEKKeyType, DbKeyType string
PKKeytype, KEKKeytype, DbKeytype string
)

var createKeysCmd = &cobra.Command{
Expand Down Expand Up @@ -50,6 +50,23 @@ func RunCreateKeys(state *config.State) error {
return err
}

// Should be own flag type
if Keytype != "" && (Keytype == "file" || Keytype == "tpm") {
state.Config.Keys.PK.Type = Keytype
state.Config.Keys.KEK.Type = Keytype
state.Config.Keys.Db.Type = Keytype
} else {
if PKKeytype != "" && (PKKeytype == "file" || PKKeytype == "tpm") {
state.Config.Keys.PK.Type = PKKeytype
}
if KEKKeytype != "" && (KEKKeytype == "file" || KEKKeytype == "tpm") {
state.Config.Keys.KEK.Type = KEKKeytype
}
if DbKeytype != "" && (DbKeytype == "file" || DbKeytype == "tpm") {
state.Config.Keys.Db.Type = DbKeytype
}
}

uuid, err := sbctl.CreateGUID(state.Fs, state.Config.GUID)
if err != nil {
return err
Expand Down Expand Up @@ -80,10 +97,10 @@ func createKeysCmdFlags(cmd *cobra.Command) {
f := cmd.Flags()
f.StringVarP(&exportPath, "export", "e", "", "export file path")
f.StringVarP(&databasePath, "database-path", "d", "", "location to create GUID file")
f.StringVarP(&Keytype, "keytype", "", "file", "key type for all keys")
f.StringVarP(&PKKeyType, "pk-type", "", "file", "PK key type (file | tpm)")
f.StringVarP(&KEKKeyType, "kek-type", "", "file", "PK key type (file | tpm)")
f.StringVarP(&DbKeyType, "db-type", "", "file", "PK key type (file | tpm)")
f.StringVarP(&Keytype, "keytype", "", "", "key type for all keys")
f.StringVarP(&PKKeytype, "pk-keytype", "", "", "PK key type (default: file)")
f.StringVarP(&KEKKeytype, "kek-keytype", "", "", "KEK key type (default: file)")
f.StringVarP(&DbKeytype, "db-keytype", "", "", "db key type (defualt: file)")
}

func init() {
Expand Down
16 changes: 15 additions & 1 deletion cmd/sbctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/google/go-tpm/tpm2/transport"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -113,8 +114,17 @@ func main() {

baseFlags(rootCmd)

// We save tpmerr and print it when we can print debug messages
rwc, tpmerr := transport.OpenTPM()
if tpmerr == nil {
defer rwc.Close()
}

state := &config.State{
Fs: fs,
Fs: fs,
TPM: func() transport.TPMCloser {
return rwc
},
Config: conf,
Efivarfs: efivarfs.NewFS().
CheckImmutable().
Expand All @@ -137,6 +147,10 @@ func main() {
}
logger := slog.New(slog.NewTextHandler(os.Stdout, opts))
slog.SetDefault(logger)

if !state.HasTPM() {
slog.Debug("can't open tpm", slog.Any("err", tpmerr))
}
}

ctx := context.WithValue(context.Background(), stateDataKey{}, state)
Expand Down
51 changes: 51 additions & 0 deletions cmd/sbctl/rotate-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type RotateKeysCmdOptions struct {
Partial stringset.StringSet
KeyFile string
CertFile string

Keytype string
PKKeytype, KEKKeytype, DbKeytype string
}

var (
Expand Down Expand Up @@ -91,10 +94,17 @@ func rotateCerts(state *config.State, hier hierarchy.Hierarchy, oldkeys *backend
func RunRotateKeys(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if err := state.Fs.MkdirAll(tmpPath, 0600); err != nil {
return fmt.Errorf("can't create tmp directory: %v", err)
}

if state.Config.Landlock {
lsm.RestrictAdditionalPaths(
landlock.RWDirs(tmpPath),
)
if err := sbctl.LandlockFromFileDatabase(state); err != nil {
return err
}
if err := lsm.Restrict(); err != nil {
return err
}
Expand Down Expand Up @@ -138,6 +148,24 @@ func rotateAllKeys(state *config.State, backupDir, newKeysDir string) error {
return fmt.Errorf("failed removing old keys: %v", err)
}

// Should be own flag type, and deduplicated
// It should be fine to modify the state here?
if rotateKeysCmdOptions.Keytype != "" && (rotateKeysCmdOptions.Keytype == "file" || rotateKeysCmdOptions.Keytype == "tpm") {
state.Config.Keys.PK.Type = rotateKeysCmdOptions.Keytype
state.Config.Keys.KEK.Type = rotateKeysCmdOptions.Keytype
state.Config.Keys.Db.Type = rotateKeysCmdOptions.Keytype
} else {
if rotateKeysCmdOptions.PKKeytype != "" && (rotateKeysCmdOptions.PKKeytype == "file" || rotateKeysCmdOptions.PKKeytype == "tpm") {
state.Config.Keys.PK.Type = rotateKeysCmdOptions.PKKeytype
}
if rotateKeysCmdOptions.KEKKeytype != "" && (rotateKeysCmdOptions.KEKKeytype == "file" || rotateKeysCmdOptions.KEKKeytype == "tpm") {
state.Config.Keys.KEK.Type = rotateKeysCmdOptions.KEKKeytype
}
if rotateKeysCmdOptions.DbKeytype != "" && (rotateKeysCmdOptions.DbKeytype == "file" || rotateKeysCmdOptions.DbKeytype == "tpm") {
state.Config.Keys.Db.Type = rotateKeysCmdOptions.DbKeytype
}
}

var newKeyHierarchy *backend.KeyHierarchy

if newKeysDir == "" {
Expand Down Expand Up @@ -228,6 +256,24 @@ func rotateKey(state *config.State, hiera string, keyPath, certPath string) erro
return fmt.Errorf("can't read efivariables: %v", err)
}

// Should be own flag type, and deduplicated
// It should be fine to modify the state here?
if rotateKeysCmdOptions.Keytype != "" && (rotateKeysCmdOptions.Keytype == "file" || rotateKeysCmdOptions.Keytype == "tpm") {
state.Config.Keys.PK.Type = rotateKeysCmdOptions.Keytype
state.Config.Keys.KEK.Type = rotateKeysCmdOptions.Keytype
state.Config.Keys.Db.Type = rotateKeysCmdOptions.Keytype
} else {
if rotateKeysCmdOptions.PKKeytype != "" && (rotateKeysCmdOptions.PKKeytype == "file" || rotateKeysCmdOptions.PKKeytype == "tpm") {
state.Config.Keys.PK.Type = rotateKeysCmdOptions.PKKeytype
}
if rotateKeysCmdOptions.KEKKeytype != "" && (rotateKeysCmdOptions.KEKKeytype == "file" || rotateKeysCmdOptions.KEKKeytype == "tpm") {
state.Config.Keys.KEK.Type = rotateKeysCmdOptions.KEKKeytype
}
if rotateKeysCmdOptions.DbKeytype != "" && (rotateKeysCmdOptions.DbKeytype == "file" || rotateKeysCmdOptions.DbKeytype == "tpm") {
state.Config.Keys.Db.Type = rotateKeysCmdOptions.DbKeytype
}
}

switch hiera {
case hierarchy.PK.String():
bk, err := backend.InitBackendFromKeys(state, newKey, newCert, hierarchy.PK)
Expand Down Expand Up @@ -274,6 +320,11 @@ func rotateKeysCmdFlags(cmd *cobra.Command) {
f.VarPF(&rotateKeysCmdOptions.Partial, "partial", "p", "rotate a key of a specific hierarchy")
f.StringVarP(&rotateKeysCmdOptions.KeyFile, "key-file", "k", "", "key file to replace (only with partial flag)")
f.StringVarP(&rotateKeysCmdOptions.CertFile, "cert-file", "c", "", "certificate file to replace (only with partial flag)")

f.StringVarP(&rotateKeysCmdOptions.Keytype, "keytype", "", "", "key type for all keys")
f.StringVarP(&rotateKeysCmdOptions.PKKeytype, "pk-keytype", "", "", "PK key type (default: file)")
f.StringVarP(&rotateKeysCmdOptions.KEKKeytype, "kek-keytype", "", "", "KEK key type (default: file)")
f.StringVarP(&rotateKeysCmdOptions.DbKeytype, "db-keytype", "", "", "db key type (defualt: file)")
}

func init() {
Expand Down
Loading

0 comments on commit 4f3cb5f

Please sign in to comment.