diff --git a/AUTHORS b/AUTHORS index 30ae7a2cf..0aa998385 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,3 +40,4 @@ List of contributors, in chronological order: * Raphael Medaer (https://github.com/rmedaer) * Raul Benencia (https://github.com/rul) * Don Kuntz (https://github.com/dkuntz2) +* Charles Duffy (https://github.com/charles-dyfis-net) diff --git a/go.mod b/go.mod index a9b0b9758..733c41ea6 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/aws/aws-sdk-go v1.25.0 github.com/cheggaaa/pb v1.0.10 github.com/fatih/color v1.7.0 // indirect + github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3 github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect github.com/gin-gonic/gin v1.1.5-0.20170702092826-d459835d2b07 + github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2 github.com/h2non/filetype v1.0.5 github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc // indirect github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d @@ -32,7 +34,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/ugorji/go v1.1.4 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 - golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc + golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405 gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect diff --git a/go.sum b/go.sum index a5fd4a1cb..d012e4151 100644 --- a/go.sum +++ b/go.sum @@ -6,12 +6,18 @@ github.com/awalterschulze/gographviz v0.0.0-20160912181450-761fd5fbb34e h1:24jix github.com/awalterschulze/gographviz v0.0.0-20160912181450-761fd5fbb34e/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= github.com/aws/aws-sdk-go v1.25.0 h1:MyXUdCesJLBvSSKYcaKeeEwxNUwUpG6/uqVYeH/Zzfo= github.com/aws/aws-sdk-go v1.25.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/charles-dyfis-net/tpmk v0.1.2-0.20210411224552-7357a2171f47 h1:NMr7ZK1dpWN9SbaxUP2z6KkJX9Tci/Y3ZdtoyY3nECc= +github.com/charles-dyfis-net/tpmk v0.1.2-0.20210411224552-7357a2171f47/go.mod h1:OMV4y1gh5ibhzmF59bQOm6klTYfZOHpotZHiD7eA/SY= github.com/cheggaaa/pb v1.0.10 h1:CNg2511WECXZ7Ja6jjyz9CMBpQOrMuP5+H5zfjgVi/Q= github.com/cheggaaa/pb v1.0.10/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/folbricht/sshtest v0.1.0/go.mod h1:HuLqb6uP2Ca4k9AwHeeivT0GLMomsBzq2PNVWO2ZL58= +github.com/folbricht/tpmk v0.1.1/go.mod h1:RhWqouWQ0rBWZ/ReJdQ+xX9RSt37oT09e7HNVFSNMBM= +github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3 h1:oRWZHiWpr89KGd6xkJaZp+HJ+WKLNS2Ub9ExC7OUew4= +github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3/go.mod h1:OMV4y1gh5ibhzmF59bQOm6klTYfZOHpotZHiD7eA/SY= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= @@ -22,10 +28,14 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2 h1:e1kceXf1XB12ZNTi2UZiXtb0+c8+zlMlQarLiTohoUY= +github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2/go.mod h1:OGEdc1XfzTyNEQyahgeXVq+E0lMq3Vu/Y3bT9EfpRnE= +github.com/google/go-tpm-tools v0.0.0-20190131232102-89d1c95730e5/go.mod h1:ApmLTU8fd5JJJ4J67y9sV16nOTR00GW2OabMwk7kSnE= github.com/h2non/filetype v1.0.5 h1:Esu2EFM5vrzNynnGQpj0nxhCkzVQh2HRY7AXUh/dyJM= github.com/h2non/filetype v1.0.5/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc h1:lWFup/SOhwcpvRJIFqx/WQis5U4SrOSyWfSqvfdF09w= github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= @@ -71,7 +81,10 @@ github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d h1:rvtR4+9N2 github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d/go.mod h1:Jm7yHrROA5tC42gyJ5EwiR8EWp0PUy0qOc4sE7Y8Uzo= github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014 h1:tne8XW3soRDJn4DIiqBc4jw+DPashtFMTSC9G0pC3ug= github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014/go.mod h1:smSuTvETRIkX95VAIWBdKoGJuUxif7NT7FgbkpVqOiA= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= @@ -82,12 +95,16 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc h1:Kx1Ke+iCR1aDjbWXgmEQGFxoHtNL49aRZGV7/+jJ41Y= golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= diff --git a/man/aptly.1 b/man/aptly.1 index fc06bade0..551df4776 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -1444,7 +1444,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\-contents\fR diff --git a/pgp/internal.go b/pgp/internal.go index 82a0a8c35..4cfd29677 100644 --- a/pgp/internal.go +++ b/pgp/internal.go @@ -5,16 +5,21 @@ import ( "fmt" "io" "io/ioutil" + "net/url" "os" "path/filepath" "sort" + "strconv" "strings" "syscall" "time" + "github.com/folbricht/tpmk" + "github.com/google/go-tpm/tpmutil" "github.com/pkg/errors" "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/clearsign" openpgp_errors "golang.org/x/crypto/openpgp/errors" "golang.org/x/crypto/openpgp/packet" @@ -39,12 +44,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 func (g *GoSigner) SetBatch(batch bool) { g.batch = batch @@ -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) { @@ -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) } } @@ -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