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,60 @@ 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 --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+ '''
5484` ,
5585 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+ },
56104 cli.StringFlag {
57105 Name : "out" ,
58106 Usage : `Path to write the reformatted result.` ,
59107 },
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 ,
60114 flags .Force ,
61115 },
62116 }
@@ -67,15 +121,53 @@ func formatAction(ctx *cli.Context) error {
67121 return err
68122 }
69123
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" )
74133
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 == "" {
79171 crtFile = "-"
80172 }
81173
@@ -144,10 +236,140 @@ func decodeCertificatePem(b []byte) ([]byte, error) {
144236 return nil , errors .Wrap (err , "error parsing certificate request" )
145237 }
146238 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
147266 default :
148267 continue
149268 }
150269 }
151270
152271 return nil , errors .Errorf ("error decoding certificate: invalid PEM block" )
153272}
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