@@ -2,13 +2,16 @@ package token
22
33import (
44 "bytes"
5+ "context"
56 "crypto"
67 "crypto/tls"
78 "encoding/json"
9+ "encoding/pem"
810 "errors"
911 "fmt"
1012 "net/http"
1113 "net/url"
14+ "os"
1215 "path"
1316
1417 "github.com/google/uuid"
@@ -19,7 +22,10 @@ import (
1922 "github.com/smallstep/cli-utils/ui"
2023 "go.step.sm/crypto/pemutil"
2124 "go.step.sm/crypto/randutil"
25+ "go.step.sm/crypto/tpm"
26+ "go.step.sm/crypto/tpm/tss2"
2227
28+ "github.com/smallstep/cli/flags"
2329 "github.com/smallstep/cli/internal/cryptoutil"
2430 "github.com/smallstep/cli/internal/httptransport"
2531)
@@ -35,6 +41,8 @@ func createCommand() cli.Command {
3541 Flags : []cli.Flag {
3642 apiURLFlag ,
3743 audienceFlag ,
44+ flags .PasswordFile ,
45+ tpmDeviceFlag ,
3846 },
3947 Description : `**step ca api token create** creates a new token for connecting to the Smallstep API.
4048
@@ -81,12 +89,14 @@ func createAction(ctx *cli.Context) (err error) {
8189 }
8290
8391 var (
84- args = ctx .Args ()
85- teamID = args .Get (0 )
86- crtFile = args .Get (1 )
87- keyFile = args .Get (2 )
88- apiURLFlag = ctx .String ("api-url" )
89- audience = ctx .String ("audience" )
92+ args = ctx .Args ()
93+ teamID = args .Get (0 )
94+ crtFile = args .Get (1 )
95+ keyFile = args .Get (2 )
96+ passwordFile = ctx .String ("password-file" )
97+ apiURLFlag = ctx .String ("api-url" )
98+ audience = ctx .String ("audience" )
99+ tpmDevice = ctx .String ("tpm-device" )
90100 )
91101
92102 parsedURL , err := url .Parse (apiURLFlag )
@@ -96,7 +106,7 @@ func createAction(ctx *cli.Context) (err error) {
96106 parsedURL .Path = path .Join (parsedURL .Path , "api/auth" )
97107 apiURL := parsedURL .String ()
98108
99- clientCert , err := createClientCertificate (crtFile , keyFile )
109+ clientCert , err := createClientCertificate (crtFile , keyFile , passwordFile , tpmDevice )
100110 if err != nil {
101111 return err
102112 }
@@ -175,7 +185,7 @@ func newRequestID() string {
175185 return requestID
176186}
177187
178- func createClientCertificate (crtFile , keyFile string ) (* tls.Certificate , error ) {
188+ func createClientCertificate (crtFile , keyFile , passwordFile , tpmDevice string ) (* tls.Certificate , error ) {
179189 certs , err := pemutil .ReadCertificateBundle (crtFile )
180190 if err != nil {
181191 return nil , fmt .Errorf ("failed reading %q: %w" , crtFile , err )
@@ -186,26 +196,71 @@ func createClientCertificate(crtFile, keyFile string) (*tls.Certificate, error)
186196 certificates [i ] = c .Raw
187197 }
188198
189- var (
190- v any
191- signer crypto.Signer
192- )
199+ pk , err := getPrivateKey (keyFile , passwordFile , tpmDevice )
200+ if err != nil {
201+ return nil , fmt .Errorf ("failed reading key from %q: %w" , keyFile , err )
202+ }
203+
204+ if _ , ok := pk .(crypto.Signer ); ! ok {
205+ return nil , fmt .Errorf ("private key type %T read from %q cannot be used as a signer" , pk , keyFile )
206+ }
207+
208+ return & tls.Certificate {
209+ Certificate : certificates ,
210+ Leaf : certs [0 ],
211+ PrivateKey : pk ,
212+ }, nil
213+ }
214+
215+ func getPrivateKey (keyFile , passwordFile , tpmDevice string ) (crypto.PrivateKey , error ) {
193216 if cryptoutil .IsKMS (keyFile ) {
194- signer , err = cryptoutil .CreateSigner (keyFile , keyFile )
217+ signer , err : = cryptoutil .CreateSigner (keyFile , keyFile )
195218 if err != nil {
196219 return nil , fmt .Errorf ("failed creating signer: %w" , err )
197220 }
198- v = signer
199- } else {
200- v , err = pemutil .Read (keyFile )
221+
222+ return signer , nil
223+ }
224+
225+ b , err := os .ReadFile (keyFile )
226+ if err != nil {
227+ return nil , err
228+ }
229+
230+ // detect the type of the PEM file. if it's a TSS2 PEM file, pemutil
231+ // can't be used to create a private key, as it does not support this
232+ // type. Support could be added, but it could require some additional
233+ // options, such as specifying the TPM device that backs the TSS2
234+ // signer.
235+ p , _ := pem .Decode (b )
236+ if p .Type != "TSS2 PRIVATE KEY" {
237+ pk , err := pemutil .Parse (b , pemutil .WithPasswordFile (passwordFile ))
201238 if err != nil {
202- return nil , fmt .Errorf ("failed reading %q : %w" , keyFile , err )
239+ return nil , fmt .Errorf ("failed parsing PEM : %w" , err )
203240 }
241+
242+ return pk , nil
204243 }
205244
206- return & tls.Certificate {
207- Certificate : certificates ,
208- Leaf : certs [0 ],
209- PrivateKey : v ,
210- }, nil
245+ key , err := tss2 .ParsePrivateKey (p .Bytes )
246+ if err != nil {
247+ return nil , fmt .Errorf ("failed creating TSS2 private key: %w" , err )
248+ }
249+
250+ var tpmOpts = []tpm.NewTPMOption {}
251+ if tpmDevice != "" {
252+ tpmOpts = append (tpmOpts , tpm .WithDeviceName (tpmDevice ))
253+ }
254+
255+ t , err := tpm .New (tpmOpts ... )
256+ if err != nil {
257+ return nil , fmt .Errorf ("failed initializing TPM: %w" , err )
258+ }
259+
260+ signer , err := tpm .CreateTSS2Signer (context .Background (), t , key )
261+ if err != nil {
262+ return nil , fmt .Errorf ("failed creating TSS2 signer: %w" , err )
263+ }
264+
265+ return signer , nil
211266}
0 commit comments