4
4
"bytes"
5
5
"crypto/x509"
6
6
"encoding/pem"
7
+ "github.com/smallstep/cli/crypto/pemutil"
7
8
"os"
8
9
9
10
"github.com/pkg/errors"
@@ -13,23 +14,28 @@ import (
13
14
"github.com/smallstep/cli/ui"
14
15
"github.com/smallstep/cli/utils"
15
16
"github.com/urfave/cli"
17
+
18
+ "software.sslmate.com/src/go-pkcs12"
16
19
)
17
20
18
21
func formatCommand () cli.Command {
19
22
return cli.Command {
20
- Name : "format" ,
21
- Action : command .ActionFunc (formatAction ),
22
- Usage : `reformat certificate` ,
23
- UsageText : `**step certificate format** <crt_file> [**--out**=<file>]` ,
23
+ Name : "format" ,
24
+ Action : command .ActionFunc (formatAction ),
25
+ Usage : `reformat certificate` ,
26
+ UsageText : `**step certificate format** <src_file> [**--crt**=<file>] [**--key**=<file>]
27
+ [**--ca**=<file>] [**--out**=<file>]` ,
24
28
Description : `**step certificate format** prints the certificate or CSR in a different format.
25
29
26
- Only 2 formats are currently supported; PEM and ASN.1 DER. This tool will convert
30
+ If either PEM or ASN.1 DER is provided as a positional argument, this tool will convert
27
31
a certificate or CSR in one format to the other.
28
32
33
+ If PFX / PKCS12 file is provided, it extracts a certificate and private key from the input.
34
+
29
35
## POSITIONAL ARGUMENTS
30
36
31
- <crt_file >
32
- : Path to a certificate or CSR file.
37
+ <src_file >
38
+ : Path to a certificate or CSR file, or .p12 file when you specify --crt/--ca option .
33
39
34
40
## EXIT CODES
35
41
@@ -51,12 +57,60 @@ Convert PEM format to DER and write to disk:
51
57
'''
52
58
$ step certificate format foo.pem --out foo.der
53
59
'''
60
+
61
+ Convert a .p12 file to a certificate and private key:
62
+
63
+ '''
64
+ $ step certificate format foo.p12 --out foo.crt --out-key foo.key
65
+ '''
66
+
67
+ Convert a .p12 file to a certificate, private key and intermediate certificates:
68
+
69
+ '''
70
+ $ step certificate format foo.p12 --out foo.crt --out-key foo.key --out-ca intermediate.crt
71
+ '''
72
+
73
+ Convert a certificate and private key to a .p12 file:
74
+
75
+ '''
76
+ $ step certificate format foo.crt --out foo.p12 --key foo.key
77
+ '''
78
+
79
+ Convert a certificate, a private key, and intermediate certificates to a .p12 file:
80
+
81
+ '''
82
+ $ step certificate format foo.crt --out foo.p12 --key foo.key --ca intermediate.crt
83
+ '''
54
84
` ,
55
85
Flags : []cli.Flag {
86
+ cli.StringFlag {
87
+ Name : "format" ,
88
+ Usage : `Target format.` ,
89
+ },
90
+ cli.StringFlag {
91
+ Name : "crt" ,
92
+ Usage : `The path to a certificate.` ,
93
+ },
94
+ cli.StringFlag {
95
+ Name : "key" ,
96
+ Usage : `The path to a private key.` ,
97
+ },
98
+ cli.StringSliceFlag {
99
+ Name : "ca" ,
100
+ Usage : `The path a CA or intermediate certificate. When converting certificates
101
+ to p12 file, Use the '--ca' flag multiple times to add
102
+ multiple CAs or intermediates.` ,
103
+ },
56
104
cli.StringFlag {
57
105
Name : "out" ,
58
106
Usage : `Path to write the reformatted result.` ,
59
107
},
108
+ cli.StringFlag {
109
+ Name : "password-file" ,
110
+ Usage : `The path to the <file> containing the password to encrypt/decrypt the .p12 file.` ,
111
+ },
112
+ flags .NoPassword ,
113
+ flags .Insecure ,
60
114
flags .Force ,
61
115
},
62
116
}
@@ -67,15 +121,53 @@ func formatAction(ctx *cli.Context) error {
67
121
return err
68
122
}
69
123
70
- var (
71
- out = ctx .String ("out" )
72
- ob []byte
73
- )
124
+ sourceFile := ctx .Args ().First ()
125
+ format := ctx .String ("format" )
126
+ crt := ctx .String ("crt" )
127
+ key := ctx .String ("key" )
128
+ ca := ctx .StringSlice ("ca" )
129
+ out := ctx .String ("out" )
130
+ passwordFile := ctx .String ("password-file" )
131
+ noPassword := ctx .Bool ("no-password" )
132
+ insecure := ctx .Bool ("insecure" )
74
133
75
- var crtFile string
76
- if ctx .NArg () == 1 {
77
- crtFile = ctx .Args ().First ()
78
- } else {
134
+ if passwordFile != "" && noPassword {
135
+ return errs .IncompatibleFlagWithFlag (ctx , "no-password" , "password-file" )
136
+ }
137
+
138
+ switch {
139
+ case format == "pem" || format == "der" :
140
+ if len (ca ) > 1 {
141
+ return errors .Errorf ("--ca option specified for multiple times when the target format is pem/der" )
142
+ }
143
+ caFile := ""
144
+ if len (ca ) == 1 {
145
+ caFile = ca [0 ]
146
+ }
147
+ if err := fromP12 (sourceFile , crt , key , caFile , passwordFile , noPassword , format ); err != nil {
148
+ return err
149
+ }
150
+ case format == "p12" :
151
+ if noPassword && ! insecure {
152
+ return errs .RequiredInsecureFlag (ctx , "no-password" )
153
+ }
154
+ if err := ToP12 (crt , sourceFile , key , ca , passwordFile , noPassword , insecure ); err != nil {
155
+ return err
156
+ }
157
+ case format == "" :
158
+ if err := interconvertPemAndDer (sourceFile , out ); err != nil {
159
+ return err
160
+ }
161
+ default :
162
+ return errors .Errorf ("unrecognized argument: --format %s" , format )
163
+ }
164
+ return nil
165
+ }
166
+
167
+ func interconvertPemAndDer (crtFile , out string ) error {
168
+ var ob []byte
169
+
170
+ if crtFile == "" {
79
171
crtFile = "-"
80
172
}
81
173
@@ -144,10 +236,140 @@ func decodeCertificatePem(b []byte) ([]byte, error) {
144
236
return nil , errors .Wrap (err , "error parsing certificate request" )
145
237
}
146
238
return csr .Raw , nil
239
+ case "RSA PRIVATE KEY" :
240
+ key , err := x509 .ParsePKCS1PrivateKey (block .Bytes )
241
+ if err != nil {
242
+ return nil , errors .Wrap (err , "error parsing RSA private key" )
243
+ }
244
+ keyBytes := x509 .MarshalPKCS1PrivateKey (key )
245
+ return keyBytes , nil
246
+ case "EC PRIVATE KEY" :
247
+ key , err := x509 .ParseECPrivateKey (block .Bytes )
248
+ if err != nil {
249
+ return nil , errors .Wrap (err , "error parsing EC private key" )
250
+ }
251
+ keyBytes , err := x509 .MarshalECPrivateKey (key )
252
+ if err != nil {
253
+ return nil , errors .Wrap (err , "error converting EC private key to DER format" )
254
+ }
255
+ return keyBytes , nil
256
+ case "PRIVATE KEY" :
257
+ key , err := x509 .ParsePKCS8PrivateKey (block .Bytes )
258
+ if err != nil {
259
+ return nil , errors .Wrap (err , "error parsing private key" )
260
+ }
261
+ keyBytes , err := x509 .MarshalPKCS8PrivateKey (key )
262
+ if err != nil {
263
+ return nil , errors .Wrap (err , "error converting private key to DER format" )
264
+ }
265
+ return keyBytes , nil
147
266
default :
148
267
continue
149
268
}
150
269
}
151
270
152
271
return nil , errors .Errorf ("error decoding certificate: invalid PEM block" )
153
272
}
273
+
274
+ func fromP12 (p12File , crtFile , keyFile , caFile , passwordFile string , noPassword bool , format string ) error {
275
+ var err error
276
+ var password string
277
+ if passwordFile != "" {
278
+ password , err = utils .ReadStringPasswordFromFile (passwordFile )
279
+ if err != nil {
280
+ return err
281
+ }
282
+ }
283
+
284
+ if password == "" && ! noPassword {
285
+ pass , err := ui .PromptPassword ("Please enter a password to decrypt the .p12 file" )
286
+ if err != nil {
287
+ return errs .Wrap (err , "error reading password" )
288
+ }
289
+ password = string (pass )
290
+ }
291
+
292
+ p12Data , err := utils .ReadFile (p12File )
293
+ if err != nil {
294
+ return errs .Wrap (err , "error reading file %s" , p12File )
295
+ }
296
+
297
+ key , crt , ca , err := pkcs12 .DecodeChain (p12Data , password )
298
+ if err != nil {
299
+ return errs .Wrap (err , "failed to decode PKCS12 data" )
300
+ }
301
+
302
+ if err := write (crtFile , format , crt ); err != nil {
303
+ return err
304
+ }
305
+
306
+ if err := writeCerts (caFile , format , ca ); err != nil {
307
+ return err
308
+ }
309
+
310
+ if err := write (keyFile , format , key ); err != nil {
311
+ return err
312
+ }
313
+
314
+ return nil
315
+ }
316
+
317
+ func writeCerts (filename , format string , certs []* x509.Certificate ) error {
318
+ if len (certs ) > 1 && format == "der" {
319
+ return errors .Errorf ("der format does not support a certificate bundle" )
320
+ }
321
+ var data []byte
322
+ for _ , cert := range certs {
323
+ b , err := toByte (cert , format )
324
+ if err != nil {
325
+ return err
326
+ }
327
+ data = append (data , b ... )
328
+ }
329
+ if err := maybeWrite (filename , data ); err != nil {
330
+ return err
331
+ }
332
+ return nil
333
+ }
334
+
335
+ func write (filename , format string , in interface {}) error {
336
+ b , err := toByte (in , format )
337
+ if err != nil {
338
+ return err
339
+ }
340
+ if err := maybeWrite (filename , b ); err != nil {
341
+ return err
342
+ }
343
+ return nil
344
+ }
345
+
346
+ func maybeWrite (filename string , out []byte ) error {
347
+ if filename == "" {
348
+ os .Stdout .Write (out )
349
+ } else {
350
+ if err := utils .WriteFile (filename , out , 0600 ); err != nil {
351
+ return err
352
+ }
353
+ }
354
+ return nil
355
+ }
356
+
357
+ func toByte (in interface {}, format string ) ([]byte , error ) {
358
+ pemblk , err := pemutil .Serialize (in )
359
+ if err != nil {
360
+ return nil , err
361
+ }
362
+ pemByte := pem .EncodeToMemory (pemblk )
363
+ switch format {
364
+ case "der" :
365
+ derByte , err := decodeCertificatePem (pemByte )
366
+ if err != nil {
367
+ return nil , err
368
+ }
369
+ return derByte , nil
370
+ case "pem" , "" :
371
+ return pemByte , nil
372
+ default :
373
+ return nil , errors .Errorf ("unsupported format: %s" , format )
374
+ }
375
+ }
0 commit comments