@@ -2,13 +2,16 @@ package token
2
2
3
3
import (
4
4
"bytes"
5
+ "context"
5
6
"crypto"
6
7
"crypto/tls"
7
8
"encoding/json"
9
+ "encoding/pem"
8
10
"errors"
9
11
"fmt"
10
12
"net/http"
11
13
"net/url"
14
+ "os"
12
15
"path"
13
16
14
17
"github.com/google/uuid"
@@ -19,7 +22,10 @@ import (
19
22
"github.com/smallstep/cli-utils/ui"
20
23
"go.step.sm/crypto/pemutil"
21
24
"go.step.sm/crypto/randutil"
25
+ "go.step.sm/crypto/tpm"
26
+ "go.step.sm/crypto/tpm/tss2"
22
27
28
+ "github.com/smallstep/cli/flags"
23
29
"github.com/smallstep/cli/internal/cryptoutil"
24
30
"github.com/smallstep/cli/internal/httptransport"
25
31
)
@@ -35,6 +41,8 @@ func createCommand() cli.Command {
35
41
Flags : []cli.Flag {
36
42
apiURLFlag ,
37
43
audienceFlag ,
44
+ flags .PasswordFile ,
45
+ tpmDeviceFlag ,
38
46
},
39
47
Description : `**step ca api token create** creates a new token for connecting to the Smallstep API.
40
48
@@ -81,12 +89,14 @@ func createAction(ctx *cli.Context) (err error) {
81
89
}
82
90
83
91
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" )
90
100
)
91
101
92
102
parsedURL , err := url .Parse (apiURLFlag )
@@ -96,7 +106,7 @@ func createAction(ctx *cli.Context) (err error) {
96
106
parsedURL .Path = path .Join (parsedURL .Path , "api/auth" )
97
107
apiURL := parsedURL .String ()
98
108
99
- clientCert , err := createClientCertificate (crtFile , keyFile )
109
+ clientCert , err := createClientCertificate (crtFile , keyFile , passwordFile , tpmDevice )
100
110
if err != nil {
101
111
return err
102
112
}
@@ -175,7 +185,7 @@ func newRequestID() string {
175
185
return requestID
176
186
}
177
187
178
- func createClientCertificate (crtFile , keyFile string ) (* tls.Certificate , error ) {
188
+ func createClientCertificate (crtFile , keyFile , passwordFile , tpmDevice string ) (* tls.Certificate , error ) {
179
189
certs , err := pemutil .ReadCertificateBundle (crtFile )
180
190
if err != nil {
181
191
return nil , fmt .Errorf ("failed reading %q: %w" , crtFile , err )
@@ -186,26 +196,71 @@ func createClientCertificate(crtFile, keyFile string) (*tls.Certificate, error)
186
196
certificates [i ] = c .Raw
187
197
}
188
198
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 ) {
193
216
if cryptoutil .IsKMS (keyFile ) {
194
- signer , err = cryptoutil .CreateSigner (keyFile , keyFile )
217
+ signer , err : = cryptoutil .CreateSigner (keyFile , keyFile )
195
218
if err != nil {
196
219
return nil , fmt .Errorf ("failed creating signer: %w" , err )
197
220
}
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 ))
201
238
if err != nil {
202
- return nil , fmt .Errorf ("failed reading %q : %w" , keyFile , err )
239
+ return nil , fmt .Errorf ("failed parsing PEM : %w" , err )
203
240
}
241
+
242
+ return pk , nil
204
243
}
205
244
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
211
266
}
0 commit comments