Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for TPM-backed signing keys (#953) #1288

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion man/aptly.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
119 changes: 91 additions & 28 deletions pgp/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ 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"
"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"
Expand All @@ -38,12 +43,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) {
Expand Down Expand Up @@ -104,12 +130,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) {
Expand All @@ -124,28 +194,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)
}
}

Expand Down Expand Up @@ -232,9 +283,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
Expand Down
Loading