44 "bytes"
55 "crypto/x509"
66 "encoding/pem"
7+ "github.com/smallstep/cli/crypto/pemutil"
78 "os"
89
910 "github.com/pkg/errors"
@@ -13,23 +14,28 @@ import (
1314 "github.com/smallstep/cli/ui"
1415 "github.com/smallstep/cli/utils"
1516 "github.com/urfave/cli"
17+
18+ "software.sslmate.com/src/go-pkcs12"
1619)
1720
1821func formatCommand () cli.Command {
1922 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>]` ,
2428 Description : `**step certificate format** prints the certificate or CSR in a different format.
2529
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
2731a certificate or CSR in one format to the other.
2832
33+ If PFX / PKCS12 file is provided, it extracts a certificate and private key from the input.
34+
2935## POSITIONAL ARGUMENTS
3036
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 .
3339
3440## EXIT CODES
3541
@@ -51,12 +57,50 @@ Convert PEM format to DER and write to disk:
5157'''
5258$ step certificate format foo.pem --out foo.der
5359'''
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+ '''
5478` ,
5579 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+ },
5699 cli.StringFlag {
57100 Name : "out" ,
58101 Usage : `Path to write the reformatted result.` ,
59102 },
103+ flags .NoPassword ,
60104 flags .Force ,
61105 },
62106 }
@@ -67,6 +111,16 @@ func formatAction(ctx *cli.Context) error {
67111 return err
68112 }
69113
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+
70124 var (
71125 out = ctx .String ("out" )
72126 ob []byte
@@ -151,3 +205,96 @@ func decodeCertificatePem(b []byte) ([]byte, error) {
151205
152206 return nil , errors .Errorf ("error decoding certificate: invalid PEM block" )
153207}
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