Skip to content

Commit e5092cf

Browse files
charles-dyfis-netneolynx
authored andcommitted
Implement support for TPM-backed signing keys (#953)
1 parent 79975bf commit e5092cf

File tree

5 files changed

+107
-30
lines changed

5 files changed

+107
-30
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ List of contributors, in chronological order:
5858
* Ryan Gonzalez (https://github.com/refi64)
5959
* Paul Cacheux (https://github.com/paulcacheux)
6060
* Nic Waller (https://github.com/sf-nwaller)
61+
* Charles Duffy (https://github.com/charles-dyfis-net)

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ require (
99
github.com/awalterschulze/gographviz v2.0.3+incompatible
1010
github.com/cavaliergopher/grab/v3 v3.0.1
1111
github.com/cheggaaa/pb v1.0.29
12+
github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3
13+
github.com/gin-contrib/sse v0.1.0 // indirect
1214
github.com/gin-gonic/gin v1.9.1
1315
github.com/go-playground/validator/v10 v10.15.4 // indirect
16+
github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2
1417
github.com/h2non/filetype v1.1.3
1518
github.com/jlaffaye/ftp v0.2.0 // indirect
1619
github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d
@@ -64,7 +67,6 @@ require (
6467
github.com/chenzhuoyu/iasm v0.9.0 // indirect
6568
github.com/cloudflare/circl v1.3.7 // indirect
6669
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
67-
github.com/gin-contrib/sse v0.1.0 // indirect
6870
github.com/go-playground/locales v0.14.1 // indirect
6971
github.com/go-playground/universal-translator v0.18.1 // indirect
7072
github.com/goccy/go-json v0.10.2 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
8686
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8787
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
8888
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
89+
github.com/folbricht/sshtest v0.1.0/go.mod h1:HuLqb6uP2Ca4k9AwHeeivT0GLMomsBzq2PNVWO2ZL58=
90+
github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3 h1:oRWZHiWpr89KGd6xkJaZp+HJ+WKLNS2Ub9ExC7OUew4=
91+
github.com/folbricht/tpmk v0.1.2-0.20210411225238-7061339773c3/go.mod h1:OMV4y1gh5ibhzmF59bQOm6klTYfZOHpotZHiD7eA/SY=
8992
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
9093
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
9194
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -115,6 +118,10 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
115118
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
116119
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
117120
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
121+
github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2 h1:e1kceXf1XB12ZNTi2UZiXtb0+c8+zlMlQarLiTohoUY=
122+
github.com/google/go-tpm v0.1.2-0.20190306182045-7a7fe86fbbf2/go.mod h1:OGEdc1XfzTyNEQyahgeXVq+E0lMq3Vu/Y3bT9EfpRnE=
123+
github.com/google/go-tpm-tools v0.0.0-20190131232102-89d1c95730e5 h1:ZP7wPiscXdeco0DkjnSLxwTksyFLG+w/sVFYZHh1sLQ=
124+
github.com/google/go-tpm-tools v0.0.0-20190131232102-89d1c95730e5/go.mod h1:ApmLTU8fd5JJJ4J67y9sV16nOTR00GW2OabMwk7kSnE=
118125
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
119126
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
120127
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -129,6 +136,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
129136
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
130137
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
131138
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
139+
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
132140
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
133141
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
134142
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -195,6 +203,7 @@ github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
195203
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
196204
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
197205
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
206+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
198207
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
199208
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
200209
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -226,6 +235,8 @@ github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d h1:rvtR4+9N2
226235
github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d/go.mod h1:Jm7yHrROA5tC42gyJ5EwiR8EWp0PUy0qOc4sE7Y8Uzo=
227236
github.com/smira/go-xz v0.1.0 h1:1zVLT1sITUKcWNysfHMLZWJ2Yh7yJfhREsgmUdK4zb0=
228237
github.com/smira/go-xz v0.1.0/go.mod h1:OmdEWnIIkuLzRLHGF4YtjDzF9VFUevEcP6YxDPRqVrs=
238+
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
239+
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
229240
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
230241
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
231242
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -250,6 +261,7 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
250261
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
251262
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
252263
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
264+
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
253265
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
254266
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
255267
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

man/aptly.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1491,7 +1491,7 @@ GPG passphrase\-file for the key (warning: could be insecure)
14911491
.
14921492
.TP
14931493
\-\fBsecret\-keyring\fR=
1494-
GPG secret keyring to use (instead of default)
1494+
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).
14951495
.
14961496
.TP
14971497
\-\fBskip\-contents\fR

pgp/internal.go

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7+
"net/url"
78
"os"
89
"path/filepath"
910
"sort"
11+
"strconv"
1012
"strings"
1113
"syscall"
1214
"time"
1315

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

1620
"github.com/ProtonMail/go-crypto/openpgp"
@@ -38,12 +42,33 @@ type GoSigner struct {
3842
passphrase, passphraseFile string
3943
batch bool
4044

45+
tpmPrivateKey *tpmk.RSAPrivateKey
4146
publicKeyring openpgp.EntityList
4247
secretKeyring openpgp.EntityList
4348
signer *openpgp.Entity
4449
signerConfig *packet.Config
4550
}
4651

52+
func findKey(keyRef string, keyring openpgp.EntityList) *openpgp.Entity {
53+
for _, signer := range keyring {
54+
key := KeyFromUint64(signer.PrimaryKey.KeyId)
55+
if key.Matches(Key(keyRef)) {
56+
return signer
57+
}
58+
59+
if !validEntity(signer) {
60+
continue
61+
}
62+
63+
for name := range signer.Identities {
64+
if strings.Contains(name, keyRef) {
65+
return signer
66+
}
67+
}
68+
}
69+
return nil
70+
}
71+
4772
// SetBatch controls whether we allowed to interact with user, for example
4873
// for getting the passphrase from stdin.
4974
func (g *GoSigner) SetBatch(batch bool) {
@@ -104,12 +129,56 @@ func (g *GoSigner) Init() error {
104129
return errors.Wrap(err, "error loading public keyring")
105130
}
106131

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

112-
if g.keyRef == "" {
169+
if g.secretKeyring == nil {
170+
// Happens if our private key is TPM-backed; means we only have a public key
171+
if g.keyRef == "" && len(g.publicKeyring) == 1 {
172+
g.signer = g.publicKeyring[0]
173+
} else if g.keyRef != "" {
174+
g.signer = findKey(g.keyRef, g.publicKeyring)
175+
if g.signer == nil {
176+
return errors.Errorf("couldn't find key for key reference %+v in public keyring", g.keyRef)
177+
}
178+
} else {
179+
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")
180+
}
181+
} else if g.keyRef == "" {
113182
// no key reference, pick the first key
114183
for _, signer := range g.secretKeyring {
115184
if !validEntity(signer) {
@@ -124,28 +193,9 @@ func (g *GoSigner) Init() error {
124193
return fmt.Errorf("looks like there are no keys in gpg, please create one (official manual: http://www.gnupg.org/gph/en/manual.html)")
125194
}
126195
} 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-
196+
g.signer = findKey(g.keyRef, g.secretKeyring)
147197
if g.signer == nil {
148-
return errors.Errorf("couldn't find key for key reference %v", g.keyRef)
198+
return errors.Errorf("couldn't find key for key reference %v in private keyring", g.keyRef)
149199
}
150200
}
151201

@@ -232,9 +282,21 @@ func (g *GoSigner) DetachedSign(source string, destination string) error {
232282
}
233283
defer signature.Close()
234284

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

240302
return nil

0 commit comments

Comments
 (0)