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,32 @@ 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>] [**--format**=<format>]` ,
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 as a positional argument, and the format is specified as "pem"/"der",
34
+ it extracts a certificate and private key from the input.
35
+
36
+ If either PEM or ASN.1 DER is provided in "--crt", "--key" and "--ca", and the format is specified as "p12",
37
+ it creates PFX / PKCS12 file from the input .
38
+
29
39
## POSITIONAL ARGUMENTS
30
40
31
- <crt_file >
32
- : Path to a certificate or CSR file.
41
+ <src_file >
42
+ : Path to a certificate or CSR file, or .p12 file when you specify --crt/--ca option .
33
43
34
44
## EXIT CODES
35
45
@@ -51,12 +61,60 @@ Convert PEM format to DER and write to disk:
51
61
'''
52
62
$ step certificate format foo.pem --out foo.der
53
63
'''
64
+
65
+ Convert a .p12 file to a certificate and private key:
66
+
67
+ '''
68
+ $ step certificate format foo.p12 --crt foo.crt --key foo.key --format pem
69
+ '''
70
+
71
+ Convert a .p12 file to a certificate, private key and intermediate certificates:
72
+
73
+ '''
74
+ $ step certificate format foo.p12 --crt foo.crt --key foo.key --ca intermediate.crt --format pem
75
+ '''
76
+
77
+ Convert a certificate and private key to a .p12 file:
78
+
79
+ '''
80
+ $ step certificate format foo.crt --crt foo.p12 --key foo.key --format p12
81
+ '''
82
+
83
+ Convert a certificate, a private key, and intermediate certificates to a .p12 file:
84
+
85
+ '''
86
+ $ step certificate format foo.crt --crt foo.p12 --key foo.key --ca intermediate.crt --format p12
87
+ '''
54
88
` ,
55
89
Flags : []cli.Flag {
90
+ cli.StringFlag {
91
+ Name : "format" ,
92
+ Usage : `Target format.` ,
93
+ },
94
+ cli.StringFlag {
95
+ Name : "crt" ,
96
+ Usage : `The path to a certificate.` ,
97
+ },
98
+ cli.StringFlag {
99
+ Name : "key" ,
100
+ Usage : `The path to a private key.` ,
101
+ },
102
+ cli.StringSliceFlag {
103
+ Name : "ca" ,
104
+ Usage : `The path a CA or intermediate certificate. When converting certificates
105
+ to p12 file, Use the '--ca' flag multiple times to add
106
+ multiple CAs or intermediates.` ,
107
+ },
56
108
cli.StringFlag {
57
109
Name : "out" ,
58
110
Usage : `Path to write the reformatted result.` ,
59
111
},
112
+ cli.StringFlag {
113
+ Name : "password-file" ,
114
+ Usage : `The path to the <file> containing the password to encrypt/decrypt the .p12 file.` ,
115
+ },
116
+ flags .NoPassword ,
117
+ flags .Insecure ,
60
118
flags .Force ,
61
119
},
62
120
}
@@ -67,15 +125,68 @@ func formatAction(ctx *cli.Context) error {
67
125
return err
68
126
}
69
127
70
- var (
71
- out = ctx .String ("out" )
72
- ob []byte
73
- )
128
+ sourceFile := ctx .Args ().First ()
129
+ format := ctx .String ("format" )
130
+ crt := ctx .String ("crt" )
131
+ key := ctx .String ("key" )
132
+ ca := ctx .StringSlice ("ca" )
133
+ out := ctx .String ("out" )
134
+ passwordFile := ctx .String ("password-file" )
135
+ noPassword := ctx .Bool ("no-password" )
136
+ insecure := ctx .Bool ("insecure" )
74
137
75
- var crtFile string
76
- if ctx .NArg () == 1 {
77
- crtFile = ctx .Args ().First ()
78
- } else {
138
+ if out != "" {
139
+ if crt != "" {
140
+ return errs .IncompatibleFlagWithFlag (ctx , "out" , "crt" )
141
+ }
142
+ if key != "" {
143
+ return errs .IncompatibleFlagWithFlag (ctx , "out" , "key" )
144
+ }
145
+ if len (ca ) != 0 {
146
+ return errs .IncompatibleFlagWithFlag (ctx , "out" , "ca" )
147
+ }
148
+ if format != "" {
149
+ return errs .IncompatibleFlagWithFlag (ctx , "out" , "format" )
150
+ }
151
+ }
152
+
153
+ if passwordFile != "" && noPassword {
154
+ return errs .IncompatibleFlagWithFlag (ctx , "no-password" , "password-file" )
155
+ }
156
+
157
+ switch {
158
+ case format == "pem" || format == "der" :
159
+ if len (ca ) > 1 {
160
+ return errors .Errorf ("--ca option specified for multiple times when the target format is pem/der" )
161
+ }
162
+ caFile := ""
163
+ if len (ca ) == 1 {
164
+ caFile = ca [0 ]
165
+ }
166
+ if err := fromP12 (sourceFile , crt , key , caFile , passwordFile , noPassword , format ); err != nil {
167
+ return err
168
+ }
169
+ case format == "p12" :
170
+ if noPassword && ! insecure {
171
+ return errs .RequiredInsecureFlag (ctx , "no-password" )
172
+ }
173
+ if err := ToP12 (crt , sourceFile , key , ca , passwordFile , noPassword , insecure ); err != nil {
174
+ return err
175
+ }
176
+ case format == "" :
177
+ if err := interconvertPemAndDer (sourceFile , out ); err != nil {
178
+ return err
179
+ }
180
+ default :
181
+ return errors .Errorf ("unrecognized argument: --format %s" , format )
182
+ }
183
+ return nil
184
+ }
185
+
186
+ func interconvertPemAndDer (crtFile , out string ) error {
187
+ var ob []byte
188
+
189
+ if crtFile == "" {
79
190
crtFile = "-"
80
191
}
81
192
@@ -144,10 +255,140 @@ func decodeCertificatePem(b []byte) ([]byte, error) {
144
255
return nil , errors .Wrap (err , "error parsing certificate request" )
145
256
}
146
257
return csr .Raw , nil
258
+ case "RSA PRIVATE KEY" :
259
+ key , err := x509 .ParsePKCS1PrivateKey (block .Bytes )
260
+ if err != nil {
261
+ return nil , errors .Wrap (err , "error parsing RSA private key" )
262
+ }
263
+ keyBytes := x509 .MarshalPKCS1PrivateKey (key )
264
+ return keyBytes , nil
265
+ case "EC PRIVATE KEY" :
266
+ key , err := x509 .ParseECPrivateKey (block .Bytes )
267
+ if err != nil {
268
+ return nil , errors .Wrap (err , "error parsing EC private key" )
269
+ }
270
+ keyBytes , err := x509 .MarshalECPrivateKey (key )
271
+ if err != nil {
272
+ return nil , errors .Wrap (err , "error converting EC private key to DER format" )
273
+ }
274
+ return keyBytes , nil
275
+ case "PRIVATE KEY" :
276
+ key , err := x509 .ParsePKCS8PrivateKey (block .Bytes )
277
+ if err != nil {
278
+ return nil , errors .Wrap (err , "error parsing private key" )
279
+ }
280
+ keyBytes , err := x509 .MarshalPKCS8PrivateKey (key )
281
+ if err != nil {
282
+ return nil , errors .Wrap (err , "error converting private key to DER format" )
283
+ }
284
+ return keyBytes , nil
147
285
default :
148
286
continue
149
287
}
150
288
}
151
289
152
290
return nil , errors .Errorf ("error decoding certificate: invalid PEM block" )
153
291
}
292
+
293
+ func fromP12 (p12File , crtFile , keyFile , caFile , passwordFile string , noPassword bool , format string ) error {
294
+ var err error
295
+ var password string
296
+ if passwordFile != "" {
297
+ password , err = utils .ReadStringPasswordFromFile (passwordFile )
298
+ if err != nil {
299
+ return err
300
+ }
301
+ }
302
+
303
+ if password == "" && ! noPassword {
304
+ pass , err := ui .PromptPassword ("Please enter a password to decrypt the .p12 file" )
305
+ if err != nil {
306
+ return errs .Wrap (err , "error reading password" )
307
+ }
308
+ password = string (pass )
309
+ }
310
+
311
+ p12Data , err := utils .ReadFile (p12File )
312
+ if err != nil {
313
+ return errs .Wrap (err , "error reading file %s" , p12File )
314
+ }
315
+
316
+ key , crt , ca , err := pkcs12 .DecodeChain (p12Data , password )
317
+ if err != nil {
318
+ return errs .Wrap (err , "failed to decode PKCS12 data" )
319
+ }
320
+
321
+ if err := write (crtFile , format , crt ); err != nil {
322
+ return err
323
+ }
324
+
325
+ if err := writeCerts (caFile , format , ca ); err != nil {
326
+ return err
327
+ }
328
+
329
+ if err := write (keyFile , format , key ); err != nil {
330
+ return err
331
+ }
332
+
333
+ return nil
334
+ }
335
+
336
+ func writeCerts (filename , format string , certs []* x509.Certificate ) error {
337
+ if len (certs ) > 1 && format == "der" {
338
+ return errors .Errorf ("der format does not support a certificate bundle" )
339
+ }
340
+ var data []byte
341
+ for _ , cert := range certs {
342
+ b , err := toByte (cert , format )
343
+ if err != nil {
344
+ return err
345
+ }
346
+ data = append (data , b ... )
347
+ }
348
+ if err := maybeWrite (filename , data ); err != nil {
349
+ return err
350
+ }
351
+ return nil
352
+ }
353
+
354
+ func write (filename , format string , in interface {}) error {
355
+ b , err := toByte (in , format )
356
+ if err != nil {
357
+ return err
358
+ }
359
+ if err := maybeWrite (filename , b ); err != nil {
360
+ return err
361
+ }
362
+ return nil
363
+ }
364
+
365
+ func maybeWrite (filename string , out []byte ) error {
366
+ if filename == "" {
367
+ os .Stdout .Write (out )
368
+ } else {
369
+ if err := utils .WriteFile (filename , out , 0600 ); err != nil {
370
+ return err
371
+ }
372
+ }
373
+ return nil
374
+ }
375
+
376
+ func toByte (in interface {}, format string ) ([]byte , error ) {
377
+ pemblk , err := pemutil .Serialize (in )
378
+ if err != nil {
379
+ return nil , err
380
+ }
381
+ pemByte := pem .EncodeToMemory (pemblk )
382
+ switch format {
383
+ case "der" :
384
+ derByte , err := decodeCertificatePem (pemByte )
385
+ if err != nil {
386
+ return nil , err
387
+ }
388
+ return derByte , nil
389
+ case "pem" , "" :
390
+ return pemByte , nil
391
+ default :
392
+ return nil , errors .Errorf ("unsupported format: %s" , format )
393
+ }
394
+ }
0 commit comments