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 option .
33
39
34
40
## EXIT CODES
35
41
@@ -51,12 +57,50 @@ 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 --crt foo.crt --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 --crt foo.crt --key foo.key --ca intermediate.crt
71
+ '''
72
+
73
+ Get certificates from "trust store" for Java applications:
74
+
75
+ '''
76
+ $ step certificate format trust.p12 --ca ca.crt
77
+ '''
54
78
` ,
55
79
Flags : []cli.Flag {
80
+ cli.StringFlag {
81
+ Name : "crt" ,
82
+ Usage : `The destination path to the <file>
83
+ to which a certificate will be extracted from .p12 file.` ,
84
+ },
85
+ cli.StringFlag {
86
+ Name : "key" ,
87
+ Usage : `The destination path to the <file>
88
+ to which a key will be extracted from .p12 file.` ,
89
+ },
90
+ cli.StringFlag {
91
+ Name : "ca" ,
92
+ Usage : `The destination path to the <file>
93
+ to which intermediate certificates will be extracted from .p12 file.` ,
94
+ },
95
+ cli.StringFlag {
96
+ Name : "password-file" ,
97
+ Usage : `The path to the <file> containing the password to decrypt the .p12 file.` ,
98
+ },
56
99
cli.StringFlag {
57
100
Name : "out" ,
58
101
Usage : `Path to write the reformatted result.` ,
59
102
},
103
+ flags .NoPassword ,
60
104
flags .Force ,
61
105
},
62
106
}
@@ -67,6 +111,16 @@ func formatAction(ctx *cli.Context) error {
67
111
return err
68
112
}
69
113
114
+ targetCrtFile := ctx .String ("crt" )
115
+ targetCAFile := ctx .String ("ca" )
116
+ // if --crt or --ca option are set, the input is .p12 file
117
+ if targetCrtFile != "" || targetCAFile != "" {
118
+ if err := formatP12Action (ctx ); err != nil {
119
+ return err
120
+ }
121
+ return nil
122
+ }
123
+
70
124
var (
71
125
out = ctx .String ("out" )
72
126
ob []byte
@@ -151,3 +205,96 @@ func decodeCertificatePem(b []byte) ([]byte, error) {
151
205
152
206
return nil , errors .Errorf ("error decoding certificate: invalid PEM block" )
153
207
}
208
+
209
+ func formatP12Action (ctx * cli.Context ) error {
210
+
211
+ p12File := ctx .Args ().Get (0 )
212
+ crtFile := ctx .String ("crt" )
213
+ keyFile := ctx .String ("key" )
214
+ caFile := ctx .String ("ca" )
215
+
216
+ var err error
217
+ var password string
218
+ if passwordFile := ctx .String ("password-file" ); passwordFile != "" {
219
+ password , err = utils .ReadStringPasswordFromFile (passwordFile )
220
+ if err != nil {
221
+ return err
222
+ }
223
+ }
224
+
225
+ if password == "" && ! ctx .Bool ("no-password" ) {
226
+ pass , err := ui .PromptPassword ("Please enter a password to decrypt the .p12 file" )
227
+ if err != nil {
228
+ return errs .Wrap (err , "error reading password" )
229
+ }
230
+ password = string (pass )
231
+ }
232
+
233
+ p12Data , err := utils .ReadFile (p12File )
234
+ if err != nil {
235
+ return errs .Wrap (err , "error reading file %s" , p12File )
236
+ }
237
+
238
+ if crtFile != "" && keyFile != "" {
239
+ // If we have a destination crt path and a key path,
240
+ // we are extracting those two from the .p12 file
241
+ key , crt , CAs , err := pkcs12 .DecodeChain (p12Data , password )
242
+ if err != nil {
243
+ return errs .Wrap (err , "failed to decode PKCS12 data" )
244
+ }
245
+
246
+ _ , err = pemutil .Serialize (key , pemutil .ToFile (keyFile , 0600 ))
247
+ if err != nil {
248
+ return errs .Wrap (err , "failed to serialize private key" )
249
+ }
250
+
251
+ _ , err = pemutil .Serialize (crt , pemutil .ToFile (crtFile , 0600 ))
252
+ if err != nil {
253
+ return errs .Wrap (err , "failed to serialize certificate" )
254
+ }
255
+
256
+ if caFile != "" {
257
+ if err := extractCerts (CAs , caFile ); err != nil {
258
+ return errs .Wrap (err , "failed to serialize CA certificates" )
259
+ }
260
+ }
261
+
262
+ } else {
263
+ // If we have only --ca flags,
264
+ // we are extracting from trust store
265
+ certs , err := pkcs12 .DecodeTrustStore (p12Data , password )
266
+ if err != nil {
267
+ return errs .Wrap (err , "failed to decode trust store" )
268
+ }
269
+ if err := extractCerts (certs , caFile ); err != nil {
270
+ return errs .Wrap (err , "failed to serialize CA certificates" )
271
+ }
272
+ }
273
+
274
+ if crtFile != "" {
275
+ ui .Printf ("Your certificate has been saved in %s.\n " , crtFile )
276
+ }
277
+ if keyFile != "" {
278
+ ui .Printf ("Your private key has been saved in %s.\n " , keyFile )
279
+ }
280
+ if caFile != "" {
281
+ ui .Printf ("Your CA certificate has been saved in %s.\n " , caFile )
282
+ }
283
+
284
+ return nil
285
+ }
286
+
287
+ func extractCerts (certs []* x509.Certificate , filename string ) error {
288
+ var data []byte
289
+ for _ , cert := range certs {
290
+ pemblk , err := pemutil .Serialize (cert )
291
+ if err != nil {
292
+ return err
293
+ }
294
+ data = append (data , pem .EncodeToMemory (pemblk )... )
295
+ }
296
+ if err := utils .WriteFile (filename , data , 0600 ); err != nil {
297
+ return err
298
+ }
299
+ return nil
300
+ }
0 commit comments