44 "bytes"
55 "crypto/x509"
66 "encoding/pem"
7+ "fmt"
8+ "github.com/smallstep/cli/crypto/pemutil"
79 "os"
810
911 "github.com/pkg/errors"
@@ -13,23 +15,28 @@ import (
1315 "github.com/smallstep/cli/ui"
1416 "github.com/smallstep/cli/utils"
1517 "github.com/urfave/cli"
18+
19+ "software.sslmate.com/src/go-pkcs12"
1620)
1721
1822func formatCommand () cli.Command {
1923 return cli.Command {
20- Name : "format" ,
21- Action : command .ActionFunc (formatAction ),
22- Usage : `reformat certificate` ,
23- UsageText : `**step certificate format** <crt_file> [**--out**=<file>]` ,
24+ Name : "format" ,
25+ Action : command .ActionFunc (formatAction ),
26+ Usage : `reformat certificate` ,
27+ UsageText : `**step certificate format** <src_file> [**--crt**=<file>] [**--key**=<file>]
28+ [**--ca**=<file>] [**--out**=<file>]` ,
2429 Description : `**step certificate format** prints the certificate or CSR in a different format.
2530
26- Only 2 formats are currently supported; PEM and ASN.1 DER. This tool will convert
31+ If either PEM or ASN.1 DER is provided as a positional argument, this tool will convert
2732a certificate or CSR in one format to the other.
2833
34+ If PFX / PKCS12 file is provided, it extracts a certificate and private key from the input.
35+
2936## POSITIONAL ARGUMENTS
3037
31- <crt_file >
32- : Path to a certificate or CSR file.
38+ <src_file >
39+ : Path to a certificate or CSR file, or .p12 file when you specify --crt/--ca option .
3340
3441## EXIT CODES
3542
@@ -51,12 +58,64 @@ Convert PEM format to DER and write to disk:
5158'''
5259$ step certificate format foo.pem --out foo.der
5360'''
61+
62+ Convert a .p12 file to a certificate and private key:
63+
64+ '''
65+ $ step certificate format foo.p12 --out foo.crt --out-key foo.key
66+ '''
67+
68+ Convert a .p12 file to a certificate, private key and intermediate certificates:
69+
70+ '''
71+ $ step certificate format foo.p12 --out foo.crt --out-key foo.key --out-ca intermediate.crt
72+ '''
73+
74+ Convert a certificate and private key to a .p12 file:
75+
76+ '''
77+ $ step certificate format foo.crt --out foo.p12 --key foo.key
78+ '''
79+
80+ Convert a certificate, a private key, and intermediate certificates to a .p12 file:
81+
82+ '''
83+ $ step certificate format foo.crt --out foo.p12 --key foo.key --ca intermediate.crt
84+ '''
5485` ,
5586 Flags : []cli.Flag {
87+ cli.StringFlag {
88+ Name : "format" ,
89+ Usage : `Target format.` ,
90+ },
91+ cli.StringFlag {
92+ Name : "key" ,
93+ Usage : `The path to the <file> containing a private key to add to the .p12 file.` ,
94+ },
95+ cli.StringSliceFlag {
96+ Name : "ca" ,
97+ Usage : `The path to the <file> containing a CA or intermediate certificate to
98+ add to the .p12 file. Use the '--ca' flag multiple times to add
99+ multiple CAs or intermediates.` ,
100+ },
56101 cli.StringFlag {
57102 Name : "out" ,
58103 Usage : `Path to write the reformatted result.` ,
59104 },
105+ cli.StringFlag {
106+ Name : "out-key" ,
107+ Usage : `Path to write the private key which is extracted from p12 file.` ,
108+ },
109+ cli.StringFlag {
110+ Name : "out-ca" ,
111+ Usage : `Path to write the intermediate certificates which are extracted from p12 file.` ,
112+ },
113+ cli.StringFlag {
114+ Name : "password-file" ,
115+ Usage : `The path to the <file> containing the password to encrypt/decrypt the .p12 file.` ,
116+ },
117+ flags .NoPassword ,
118+ flags .Insecure ,
60119 flags .Force ,
61120 },
62121 }
@@ -67,15 +126,51 @@ func formatAction(ctx *cli.Context) error {
67126 return err
68127 }
69128
70- var (
71- out = ctx .String ("out" )
72- ob []byte
73- )
129+ sourceFile := ctx .Args ().First ()
130+ format := ctx .String ("format" )
131+ key := ctx .String ("key" )
132+ ca := ctx .StringSlice ("ca" )
133+ out := ctx .String ("out" )
134+ outKey := ctx .String ("out-key" )
135+ outCA := ctx .String ("out-ca" )
136+ passwordFile := ctx .String ("password-file" )
137+ noPassword := ctx .Bool ("no-password" )
138+ insecure := ctx .Bool ("insecure" )
74139
75- var crtFile string
76- if ctx .NArg () == 1 {
77- crtFile = ctx .Args ().First ()
78- } else {
140+ if passwordFile != "" && noPassword {
141+ return errs .IncompatibleFlagWithFlag (ctx , "no-password" , "password-file" )
142+ }
143+
144+ switch {
145+ // Format P12 to pem/der
146+ // We can default source format to p12 if --out-key or --out-ca are passed
147+ case format == "pem" || format == "der" || outKey != "" :
148+ if err := fromP12 (sourceFile , out , outKey , outCA , passwordFile , noPassword ); err != nil {
149+ return err
150+ }
151+ // Format PEM to P12
152+ // We can default target format to p12 if --key or --ca are passed
153+ case format == "p12" || key != "" || len (ca ) != 0 :
154+ if noPassword && ! insecure {
155+ return errs .RequiredInsecureFlag (ctx , "no-password" )
156+ }
157+ if err := ToP12 (out , sourceFile , key , ca , passwordFile , noPassword , insecure ); err != nil {
158+ return err
159+ }
160+ case format == "" :
161+ if err := interconvertPemAndDer (sourceFile , out ); err != nil {
162+ return err
163+ }
164+ default :
165+ return errors .Errorf ("unrecognized argument: --format %s" , format )
166+ }
167+ return nil
168+ }
169+
170+ func interconvertPemAndDer (crtFile , out string ) error {
171+ var ob []byte
172+
173+ if crtFile == "" {
79174 crtFile = "-"
80175 }
81176
@@ -151,3 +246,82 @@ func decodeCertificatePem(b []byte) ([]byte, error) {
151246
152247 return nil , errors .Errorf ("error decoding certificate: invalid PEM block" )
153248}
249+
250+ func fromP12 (p12File , crtFile , keyFile , caFile , passwordFile string , noPassword bool ) error {
251+ var err error
252+ var password string
253+ if passwordFile != "" {
254+ password , err = utils .ReadStringPasswordFromFile (passwordFile )
255+ if err != nil {
256+ return err
257+ }
258+ }
259+
260+ if password == "" && ! noPassword {
261+ pass , err := ui .PromptPassword ("Please enter a password to decrypt the .p12 file" )
262+ if err != nil {
263+ return errs .Wrap (err , "error reading password" )
264+ }
265+ password = string (pass )
266+ }
267+
268+ p12Data , err := utils .ReadFile (p12File )
269+ if err != nil {
270+ return errs .Wrap (err , "error reading file %s" , p12File )
271+ }
272+
273+ key , crt , ca , err := pkcs12 .DecodeChain (p12Data , password )
274+ if err != nil {
275+ return errs .Wrap (err , "failed to decode PKCS12 data" )
276+ }
277+
278+ if err := write (crtFile , fmt .Sprintf ("Your certificate has been saved in %s.\n " , crtFile ), crt ); err != nil {
279+ return err
280+ }
281+
282+ if err := writeCerts (caFile , fmt .Sprintf ("Your CA certificates have been saved in %s.\n " , caFile ), ca ); err != nil {
283+ return err
284+ }
285+
286+ if err := write (keyFile , fmt .Sprintf ("Your private key has been saved in %s.\n " , keyFile ), key ); err != nil {
287+ return err
288+ }
289+
290+ return nil
291+ }
292+
293+ func writeCerts (filename , msg string , certs []* x509.Certificate ) error {
294+ var data []byte
295+ for _ , cert := range certs {
296+ pemblk , err := pemutil .Serialize (cert )
297+ if err != nil {
298+ return err
299+ }
300+ data = append (data , pem .EncodeToMemory (pemblk )... )
301+ }
302+ if filename == "" {
303+ os .Stdout .Write (data )
304+ } else {
305+ if err := utils .WriteFile (filename , data , 0600 ); err != nil {
306+ return err
307+ }
308+ ui .Printf (msg )
309+ }
310+ return nil
311+ }
312+
313+ func write (filename , msg string , in interface {}) error {
314+ if filename != "" {
315+ if _ , err := pemutil .Serialize (in , pemutil .ToFile (filename , 0600 )); err != nil {
316+ return err
317+ }
318+ ui .Printf (msg )
319+ } else {
320+ pemblk , err := pemutil .Serialize (in )
321+ if err != nil {
322+ return err
323+ }
324+ pem .Encode (os .Stdout , pemblk )
325+ }
326+ return nil
327+ }
0 commit comments