From 43a4d06dbe4c0c09e806ca50c5905168658bd889 Mon Sep 17 00:00:00 2001 From: Charles Duffy Date: Sun, 11 Apr 2021 18:07:52 -0500 Subject: [PATCH 1/2] Implement support for TPM-backed signing keys (#953) --- AUTHORS | 1 + man/aptly.1 | 2 +- pgp/internal.go | 118 ++++++++++++++++++++++++++++++++++++------------ 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/AUTHORS b/AUTHORS index d62f1573de..7272ad37a9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -68,3 +68,4 @@ List of contributors, in chronological order: * Blake Kostner (https://github.com/btkostner) * Leigh London (https://github.com/leighlondon) * Gordian Schoenherr (https://github.com/schoenherrg) +* Charles Duffy (https://github.com/charles-dyfis-net) diff --git a/man/aptly.1 b/man/aptly.1 index bd6ad22317..2af59c922e 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -1622,7 +1622,7 @@ GPG passphrase\-file for the key (warning: could be insecure) . .TP \-\fBsecret\-keyring\fR= -GPG secret keyring to use (instead of default) +GPG secret keyring to use (instead of default); may be of the form \fBtpm://HANDLE?dev=DEVICE\fR to use a TPM-backed key if the selected \fBgpgProvider\fR is \fBinternal\fR, where \fBHANDLE\fR is of the form \fB0x81000003\fR, and \fBdev\fR is a (URL-escaped) value similar to \fB/dev/tpmrm0\fR (which happens to be the default if not given). . .TP \-\fBskip\-bz2\fR diff --git a/pgp/internal.go b/pgp/internal.go index 9796295c88..0ad89f58b1 100644 --- a/pgp/internal.go +++ b/pgp/internal.go @@ -4,13 +4,17 @@ import ( "bytes" "fmt" "io" + "net/url" "os" "path/filepath" "sort" + "strconv" "strings" "syscall" "time" + "github.com/folbricht/tpmk" + "github.com/google/go-tpm/tpmutil" "github.com/pkg/errors" "github.com/ProtonMail/go-crypto/openpgp" @@ -38,12 +42,33 @@ type GoSigner struct { passphrase, passphraseFile string batch bool + tpmPrivateKey *tpmk.RSAPrivateKey publicKeyring openpgp.EntityList secretKeyring openpgp.EntityList signer *openpgp.Entity signerConfig *packet.Config } +func findKey(keyRef string, keyring openpgp.EntityList) *openpgp.Entity { + for _, signer := range keyring { + key := KeyFromUint64(signer.PrimaryKey.KeyId) + if key.Matches(Key(keyRef)) { + return signer + } + + if !validEntity(signer) { + continue + } + + for name := range signer.Identities { + if strings.Contains(name, keyRef) { + return signer + } + } + } + return nil +} + // SetBatch controls whether we allowed to interact with user, for example // for getting the passphrase from stdin. func (g *GoSigner) SetBatch(batch bool) { @@ -104,12 +129,56 @@ func (g *GoSigner) Init() error { return errors.Wrap(err, "error loading public keyring") } - g.secretKeyring, err = loadKeyRing(g.secretKeyringFile, false) - if err != nil { - return errors.Wrap(err, "error load secret keyring") + if strings.HasPrefix(g.secretKeyringFile, "tpm://") { + // Expected form of tpm://0x81000002 -- optionally with query parameters holding extra values + // f/e, ?dev=%2Fdev%2Ftpmrm1 to specify the device as /dev/tpmrm1; or ?dev=sim for simulator + tpmSecretURL, err := url.Parse(g.secretKeyringFile) + if err != nil { + return errors.Wrap(err, "parsing TPM URI") + } + tpmQueryArgs := tpmSecretURL.Query() + devStrings, hasDev := tpmQueryArgs["dev"] + tpmDevFilename := "/dev/tpmrm0" + if hasDev && len(devStrings) != 0 { + if len(devStrings) > 1 { + return errors.Errorf("Parsing TPM address, more than one device name found") + } + tpmDevFilename = devStrings[0] + } + tpmDev, err := tpmk.OpenDevice(tpmDevFilename) + if err != nil { + return errors.Wrap(err, "opening TPM device") + } + tpmHandleInt, err := strconv.ParseUint(tpmSecretURL.Host, 0, 32) + if err != nil { + return errors.Wrap(err, "parsing TPM URI host as integer handle") + } + tpmHandle := tpmutil.Handle(tpmHandleInt) + privKey, err := tpmk.NewRSAPrivateKey(tpmDev, tpmHandle, g.passphrase) + if err != nil { + return errors.Wrap(err, "opening TPM key handle") + } + g.tpmPrivateKey = &privKey + } else { + g.secretKeyring, err = loadKeyRing(g.secretKeyringFile, false) + if err != nil { + return errors.Wrap(err, "error load secret keyring") + } } - if g.keyRef == "" { + if g.secretKeyring == nil { + // Happens if our private key is TPM-backed; means we only have a public key + if g.keyRef == "" && len(g.publicKeyring) == 1 { + g.signer = g.publicKeyring[0] + } else if g.keyRef != "" { + g.signer = findKey(g.keyRef, g.publicKeyring) + if g.signer == nil { + return errors.Errorf("couldn't find key for key reference %+v in public keyring", g.keyRef) + } + } else { + return errors.Errorf("must either only have our signing key in the public keyring, or provide the identity of the signing key when in tpm mode") + } + } else if g.keyRef == "" { // no key reference, pick the first key for _, signer := range g.secretKeyring { if !validEntity(signer) { @@ -124,28 +193,9 @@ func (g *GoSigner) Init() error { return fmt.Errorf("looks like there are no keys in gpg, please create one (official manual: http://www.gnupg.org/gph/en/manual.html)") } } else { - pickKeyLoop: - for _, signer := range g.secretKeyring { - key := KeyFromUint64(signer.PrimaryKey.KeyId) - if key.Matches(Key(g.keyRef)) { - g.signer = signer - break - } - - if !validEntity(signer) { - continue - } - - for name := range signer.Identities { - if strings.Contains(name, g.keyRef) { - g.signer = signer - break pickKeyLoop - } - } - } - + g.signer = findKey(g.keyRef, g.secretKeyring) if g.signer == nil { - return errors.Errorf("couldn't find key for key reference %v", g.keyRef) + return errors.Errorf("couldn't find key for key reference %v in private keyring", g.keyRef) } } @@ -232,9 +282,21 @@ func (g *GoSigner) DetachedSign(source string, destination string) error { } defer signature.Close() - err = openpgp.ArmoredDetachSign(signature, g.signer, message, g.signerConfig) - if err != nil { - return errors.Wrap(err, "error creating detached signature") + if g.tpmPrivateKey != nil { + encoder, err := armor.Encode(signature, openpgp.SignatureType, nil) + if err != nil { + return errors.Wrap(err, "error creating armoring encoder") + } + defer encoder.Close() + err = tpmk.OpenPGPDetachSign(encoder, g.signer, message, nil, g.tpmPrivateKey) + if err != nil { + return errors.Wrap(err, "error creating detached signature with TPM-backed key") + } + } else { + err = openpgp.ArmoredDetachSign(signature, g.signer, message, g.signerConfig) + if err != nil { + return errors.Wrap(err, "error creating detached signature") + } } return nil From a7862627a917dd226a3ac6b29d5b6303e615659b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Wed, 24 Apr 2024 22:47:13 +0200 Subject: [PATCH 2/2] fix build --- pgp/internal.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pgp/internal.go b/pgp/internal.go index 0ad89f58b1..d09d0974eb 100644 --- a/pgp/internal.go +++ b/pgp/internal.go @@ -19,6 +19,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/clearsign" + "github.com/ProtonMail/go-crypto/openpgp/armor" openpgp_errors "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/packet" "golang.org/x/term"