Skip to content

Commit 050cb84

Browse files
Implement support for TPM-backed signing keys (#953)
1 parent cbf0416 commit 050cb84

File tree

5 files changed

+111
-32
lines changed

5 files changed

+111
-32
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ List of contributors, in chronological order:
4040
* Raphael Medaer (https://github.com/rmedaer)
4141
* Raul Benencia (https://github.com/rul)
4242
* Don Kuntz (https://github.com/dkuntz2)
43+
* Charles Duffy (https://github.com/charles-dyfis-net)

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ require (
99
github.com/aws/aws-sdk-go v1.25.0
1010
github.com/cheggaaa/pb v1.0.10
1111
github.com/fatih/color v1.7.0 // indirect
12+
github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3
1213
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
1314
github.com/gin-gonic/gin v1.1.5-0.20170702092826-d459835d2b07
15+
github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2
16+
github.com/google/go-tpm-tools v0.0.0-20190131232102-89d1c95730e5
1417
github.com/h2non/filetype v1.0.5
1518
github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc // indirect
1619
github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d
@@ -32,7 +35,7 @@ require (
3235
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
3336
github.com/ugorji/go v1.1.4
3437
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
35-
golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc
38+
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576
3639
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223
3740
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405
3841
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect

go.sum

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
1212
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1313
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
1414
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
15+
github.com/folbricht/sshtest v0.1.0/go.mod h1:HuLqb6uP2Ca4k9AwHeeivT0GLMomsBzq2PNVWO2ZL58=
16+
github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3 h1:oRWZHiWpr89KGd6xkJaZp+HJ+WKLNS2Ub9ExC7OUew4=
17+
github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3/go.mod h1:OMV4y1gh5ibhzmF59bQOm6klTYfZOHpotZHiD7eA/SY=
1518
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
1619
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
1720
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
@@ -22,10 +25,15 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
2225
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2326
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
2427
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
28+
github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2 h1:e1kceXf1XB12ZNTi2UZiXtb0+c8+zlMlQarLiTohoUY=
29+
github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2/go.mod h1:OGEdc1XfzTyNEQyahgeXVq+E0lMq3Vu/Y3bT9EfpRnE=
30+
github.com/google/go-tpm-tools v0.0.0-20190131232102-89d1c95730e5 h1:ZP7wPiscXdeco0DkjnSLxwTksyFLG+w/sVFYZHh1sLQ=
31+
github.com/google/go-tpm-tools v0.0.0-20190131232102-89d1c95730e5/go.mod h1:ApmLTU8fd5JJJ4J67y9sV16nOTR00GW2OabMwk7kSnE=
2532
github.com/h2non/filetype v1.0.5 h1:Esu2EFM5vrzNynnGQpj0nxhCkzVQh2HRY7AXUh/dyJM=
2633
github.com/h2non/filetype v1.0.5/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
2734
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
2835
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
36+
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
2937
github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc h1:lWFup/SOhwcpvRJIFqx/WQis5U4SrOSyWfSqvfdF09w=
3038
github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
3139
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
@@ -71,7 +79,10 @@ github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d h1:rvtR4+9N2
7179
github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d/go.mod h1:Jm7yHrROA5tC42gyJ5EwiR8EWp0PUy0qOc4sE7Y8Uzo=
7280
github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014 h1:tne8XW3soRDJn4DIiqBc4jw+DPashtFMTSC9G0pC3ug=
7381
github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014/go.mod h1:smSuTvETRIkX95VAIWBdKoGJuUxif7NT7FgbkpVqOiA=
82+
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
83+
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
7484
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
85+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
7586
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
7687
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
7788
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
@@ -80,14 +91,15 @@ github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
8091
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
8192
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
8293
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
83-
golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc h1:Kx1Ke+iCR1aDjbWXgmEQGFxoHtNL49aRZGV7/+jJ41Y=
84-
golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
94+
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
95+
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
8596
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
8697
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
8798
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
8899
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
89100
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
90101
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
102+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
91103
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
92104
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
93105
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

man/aptly.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1444,7 +1444,7 @@ GPG passphrase\-file for the key (warning: could be insecure)
14441444
.
14451445
.TP
14461446
\-\fBsecret\-keyring\fR=
1447-
GPG secret keyring to use (instead of default)
1447+
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).
14481448
.
14491449
.TP
14501450
\-\fBskip\-contents\fR

pgp/internal.go

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import (
55
"fmt"
66
"io"
77
"io/ioutil"
8+
"net/url"
89
"os"
910
"path/filepath"
1011
"sort"
12+
"strconv"
1113
"strings"
1214
"syscall"
1315
"time"
1416

17+
"github.com/folbricht/tpmk"
18+
"github.com/google/go-tpm/tpmutil"
1519
"github.com/pkg/errors"
1620

1721
"golang.org/x/crypto/openpgp"
22+
"golang.org/x/crypto/openpgp/armor"
1823
"golang.org/x/crypto/openpgp/clearsign"
1924
openpgp_errors "golang.org/x/crypto/openpgp/errors"
2025
"golang.org/x/crypto/openpgp/packet"
@@ -39,12 +44,33 @@ type GoSigner struct {
3944
passphrase, passphraseFile string
4045
batch bool
4146

47+
tpmPrivateKey *tpmk.RSAPrivateKey
4248
publicKeyring openpgp.EntityList
4349
secretKeyring openpgp.EntityList
4450
signer *openpgp.Entity
4551
signerConfig *packet.Config
4652
}
4753

54+
func findKey(keyRef string, keyring openpgp.EntityList) *openpgp.Entity {
55+
for _, signer := range keyring {
56+
key := KeyFromUint64(signer.PrimaryKey.KeyId)
57+
if key.Matches(Key(keyRef)) {
58+
return signer
59+
}
60+
61+
if !validEntity(signer) {
62+
continue
63+
}
64+
65+
for name := range signer.Identities {
66+
if strings.Contains(name, keyRef) {
67+
return signer
68+
}
69+
}
70+
}
71+
return nil
72+
}
73+
4874
// SetBatch controls whether we allowed to interact with user
4975
func (g *GoSigner) SetBatch(batch bool) {
5076
g.batch = batch
@@ -104,12 +130,56 @@ func (g *GoSigner) Init() error {
104130
return errors.Wrap(err, "error loading public keyring")
105131
}
106132

107-
g.secretKeyring, err = loadKeyRing(g.secretKeyringFile, false)
108-
if err != nil {
109-
return errors.Wrap(err, "error load secret keyring")
133+
if strings.HasPrefix(g.secretKeyringFile, "tpm://") {
134+
// Expected form of tpm://0x81000002 -- optionally with query parameters holding extra values
135+
// f/e, ?dev=%2Fdev%2Ftpmrm1 to specify the device as /dev/tpmrm1; or ?dev=sim for simulator
136+
tpmSecretURL, err := url.Parse(g.secretKeyringFile)
137+
if err != nil {
138+
return errors.Wrap(err, "parsing TPM URI")
139+
}
140+
tpmQueryArgs := tpmSecretURL.Query()
141+
devStrings, hasDev := tpmQueryArgs["dev"]
142+
tpmDevFilename := "/dev/tpmrm0"
143+
if hasDev && len(devStrings) != 0 {
144+
if len(devStrings) > 1 {
145+
return errors.Errorf("Parsing TPM address, more than one device name found")
146+
}
147+
tpmDevFilename = devStrings[0]
148+
}
149+
tpmDev, err := tpmk.OpenDevice(tpmDevFilename)
150+
if err != nil {
151+
return errors.Wrap(err, "opening TPM device")
152+
}
153+
tpmHandleInt, err := strconv.ParseUint(tpmSecretURL.Host, 0, 32)
154+
if err != nil {
155+
return errors.Wrap(err, "parsing TPM URI host as integer handle")
156+
}
157+
tpmHandle := tpmutil.Handle(tpmHandleInt)
158+
privKey, err := tpmk.NewRSAPrivateKey(tpmDev, tpmHandle, g.passphrase)
159+
if err != nil {
160+
return errors.Wrap(err, "opening TPM key handle")
161+
}
162+
g.tpmPrivateKey = &privKey
163+
} else {
164+
g.secretKeyring, err = loadKeyRing(g.secretKeyringFile, false)
165+
if err != nil {
166+
return errors.Wrap(err, "error load secret keyring")
167+
}
110168
}
111169

112-
if g.keyRef == "" {
170+
if g.secretKeyring == nil {
171+
// Happens if our private key is TPM-backed; means we only have a public key
172+
if g.keyRef == "" && len(g.publicKeyring) == 1 {
173+
g.signer = g.publicKeyring[0]
174+
} else if g.keyRef != "" {
175+
g.signer = findKey(g.keyRef, g.publicKeyring)
176+
if g.signer == nil {
177+
return errors.Errorf("couldn't find key for key reference %+v in public keyring", g.keyRef)
178+
}
179+
} else {
180+
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")
181+
}
182+
} else if g.keyRef == "" {
113183
// no key reference, pick the first key
114184
for _, signer := range g.secretKeyring {
115185
if !validEntity(signer) {
@@ -124,28 +194,9 @@ func (g *GoSigner) Init() error {
124194
return fmt.Errorf("looks like there are no keys in gpg, please create one (official manual: http://www.gnupg.org/gph/en/manual.html)")
125195
}
126196
} else {
127-
pickKeyLoop:
128-
for _, signer := range g.secretKeyring {
129-
key := KeyFromUint64(signer.PrimaryKey.KeyId)
130-
if key.Matches(Key(g.keyRef)) {
131-
g.signer = signer
132-
break
133-
}
134-
135-
if !validEntity(signer) {
136-
continue
137-
}
138-
139-
for name := range signer.Identities {
140-
if strings.Contains(name, g.keyRef) {
141-
g.signer = signer
142-
break pickKeyLoop
143-
}
144-
}
145-
}
146-
197+
g.signer = findKey(g.keyRef, g.secretKeyring)
147198
if g.signer == nil {
148-
return errors.Errorf("couldn't find key for key reference %v", g.keyRef)
199+
return errors.Errorf("couldn't find key for key reference %v in private keyring", g.keyRef)
149200
}
150201
}
151202

@@ -232,9 +283,21 @@ func (g *GoSigner) DetachedSign(source string, destination string) error {
232283
}
233284
defer signature.Close()
234285

235-
err = openpgp.ArmoredDetachSign(signature, g.signer, message, g.signerConfig)
236-
if err != nil {
237-
return errors.Wrap(err, "error creating detached signature")
286+
if g.tpmPrivateKey != nil {
287+
encoder, err := armor.Encode(signature, openpgp.SignatureType, nil)
288+
if err != nil {
289+
return errors.Wrap(err, "error creating armoring encoder")
290+
}
291+
defer encoder.Close()
292+
err = tpmk.OpenPGPDetachSign(encoder, g.signer, message, nil, g.tpmPrivateKey)
293+
if err != nil {
294+
return errors.Wrap(err, "error creating detached signature with TPM-backed key")
295+
}
296+
} else {
297+
err = openpgp.ArmoredDetachSign(signature, g.signer, message, g.signerConfig)
298+
if err != nil {
299+
return errors.Wrap(err, "error creating detached signature")
300+
}
238301
}
239302

240303
return nil

0 commit comments

Comments
 (0)