@@ -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
4975func (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