-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #487
- Loading branch information
Showing
3 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package certificate | ||
|
||
import ( | ||
"crypto/x509" | ||
"encoding/pem" | ||
|
||
"github.com/smallstep/cli/command" | ||
"github.com/smallstep/cli/crypto/pemutil" | ||
"github.com/smallstep/cli/errs" | ||
"github.com/smallstep/cli/flags" | ||
"github.com/smallstep/cli/ui" | ||
"github.com/smallstep/cli/utils" | ||
"github.com/urfave/cli" | ||
|
||
"software.sslmate.com/src/go-pkcs12" | ||
) | ||
|
||
func extractCommand() cli.Command { | ||
return cli.Command{ | ||
Name: "extract", | ||
Action: command.ActionFunc(extractAction), | ||
Usage: `extract a .p12 file`, | ||
UsageText: `step certificate extract <p12-path> [<crt-path>] [<key-path>] | ||
[**--ca**=<file>] [**--password-file**=<file>]`, | ||
Description: `**step certificate extract** extracts a certificate and private key | ||
from a .p12 (PFX / PKCS12) file. | ||
## EXIT CODES | ||
This command returns 0 on success and \>0 if any error occurs. | ||
## EXAMPLES | ||
Extract a certificate and a private key from a .p12 file: | ||
''' | ||
$ step certificate extract foo.p12 foo.crt foo.key | ||
''' | ||
Extract a certificate, private key and intermediate certidicates from a .p12 file: | ||
''' | ||
$ step certificate extract foo.p12 foo.crt foo.key --ca intermediate.crt | ||
''' | ||
Extract certificates from "trust store" for Java applications: | ||
''' | ||
$ step certificate extract trust.p12 --ca ca.crt | ||
'''`, | ||
Flags: []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "ca", | ||
Usage: `The path to the <file> containing a CA or intermediate certificate to | ||
add to the .p12 file. Use the '--ca' flag multiple times to add | ||
multiple CAs or intermediates.`, | ||
}, | ||
cli.StringFlag{ | ||
Name: "password-file", | ||
Usage: `The path to the <file> containing the password to decrypt the .p12 file.`, | ||
}, | ||
flags.NoPassword, | ||
}, | ||
} | ||
} | ||
|
||
func extractAction(ctx *cli.Context) error { | ||
if err := errs.MinMaxNumberOfArguments(ctx, 1, 3); err != nil { | ||
return err | ||
} | ||
|
||
p12File := ctx.Args().Get(0) | ||
crtFile := ctx.Args().Get(1) | ||
keyFile := ctx.Args().Get(2) | ||
caFile := ctx.String("ca") | ||
|
||
var err error | ||
var password string | ||
if passwordFile := ctx.String("password-file"); passwordFile != "" { | ||
password, err = utils.ReadStringPasswordFromFile(passwordFile) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if password == "" && !ctx.Bool("no-password") { | ||
pass, err := ui.PromptPassword("Please enter a password to decrypt the .p12 file") | ||
if err != nil { | ||
return errs.Wrap(err, "error reading password") | ||
} | ||
password = string(pass) | ||
} | ||
|
||
p12Data, err := utils.ReadFile(p12File) | ||
if err != nil { | ||
return errs.Wrap(err, "error reading file %s", p12File) | ||
} | ||
|
||
if crtFile != "" && keyFile != "" { | ||
// If we have a destination crt path and a key path, | ||
// we are extracting a .p12 file | ||
key, crt, CAs, err := pkcs12.DecodeChain(p12Data, password) | ||
if err != nil { | ||
return errs.Wrap(err, "failed to decode PKCS12 data") | ||
} | ||
|
||
_, err = pemutil.Serialize(key, pemutil.ToFile(keyFile, 0600)) | ||
if err != nil { | ||
return errs.Wrap(err, "failed to serialize private key") | ||
} | ||
|
||
_, err = pemutil.Serialize(crt, pemutil.ToFile(crtFile, 0600)) | ||
if err != nil { | ||
return errs.Wrap(err, "failed to serialize certificate") | ||
} | ||
|
||
if caFile != "" { | ||
if err := extractCerts(CAs, caFile); err != nil { | ||
return errs.Wrap(err, "failed to serialize CA certificates") | ||
} | ||
} | ||
|
||
} else { | ||
// If we have only --ca flags, | ||
// we are extracting from trust store | ||
certs, err := pkcs12.DecodeTrustStore(p12Data, password) | ||
if err != nil { | ||
return errs.Wrap(err, "failed to decode trust store") | ||
} | ||
if err := extractCerts(certs, caFile); err != nil { | ||
return errs.Wrap(err, "failed to serialize CA certificates") | ||
} | ||
} | ||
|
||
if crtFile != "" { | ||
ui.Printf("Your certificate has been saved in %s.\n", crtFile) | ||
} | ||
if keyFile != "" { | ||
ui.Printf("Your private key has been saved in %s.\n", keyFile) | ||
} | ||
if caFile != "" { | ||
ui.Printf("Your CA certificate has been saved in %s.\n", caFile) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func extractCerts(certs []*x509.Certificate, filename string) error { | ||
var data []byte | ||
for _, cert := range certs { | ||
pemblk, err := pemutil.Serialize(cert) | ||
if err != nil { | ||
return err | ||
} | ||
data = append(data, pem.EncodeToMemory(pemblk)...) | ||
} | ||
if err := utils.WriteFile(filename, data, 0600); err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
//go:build integration | ||
|
||
package integration | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/smallstep/assert" | ||
"github.com/smallstep/cli/crypto/pemutil" | ||
"github.com/smallstep/cli/utils" | ||
) | ||
|
||
func TestCertificateP12(t *testing.T) { | ||
setup() | ||
t.Run("extracted cert and key are equal to p12 inputs", func(t *testing.T) { | ||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate p12 %s %s %s", temp("foo.p12"), temp("foo.crt"), temp("foo.key"))). | ||
setFlag("no-password", ""). | ||
setFlag("insecure", ""). | ||
run() | ||
|
||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate extract %s %s %s", temp("foo.p12"), temp("foo_out.crt"), temp("foo_out.key"))). | ||
setFlag("no-password", ""). | ||
run() | ||
|
||
foo_crt, _ := pemutil.ReadCertificate(temp("foo.crt")) | ||
foo_crt_out, _ := pemutil.ReadCertificate(temp("foo_out.crt")) | ||
assert.Equals(t, foo_crt, foo_crt_out) | ||
|
||
foo_key, _ := utils.ReadFile(temp("foo.key")) | ||
foo_out_key, _ := utils.ReadFile(temp("foo_out.key")) | ||
assert.Equals(t, foo_key, foo_out_key) | ||
}) | ||
|
||
t.Run("extracted trust store is equal to p12 input", func(t *testing.T) { | ||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate p12 %s", temp("truststore.p12"))). | ||
setFlag("ca", temp("intermediate-ca.crt")). | ||
setFlag("no-password", ""). | ||
setFlag("insecure", ""). | ||
run() | ||
|
||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate extract %s", temp("truststore.p12"))). | ||
setFlag("ca", temp("intermediate-ca_out.crt")). | ||
setFlag("no-password", ""). | ||
run() | ||
|
||
ca, _ := pemutil.ReadCertificate(temp("intermediate-ca.crt")) | ||
ca_out, _ := pemutil.ReadCertificate(temp("intermediate-ca_out.crt")) | ||
assert.Equals(t, ca, ca_out) | ||
}) | ||
} | ||
|
||
func setup() { | ||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate create root-ca %s %s", temp("root-ca.crt"), temp("root-ca.key"))). | ||
setFlag("profile", "root-ca"). | ||
setFlag("no-password", ""). | ||
setFlag("insecure", ""). | ||
run() | ||
|
||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate create intermediate-ca %s %s", temp("intermediate-ca.crt"), temp("intermediate-ca.key"))). | ||
setFlag("profile", "intermediate-ca"). | ||
setFlag("ca", temp("root-ca.crt")). | ||
setFlag("ca-key", temp("root-ca.key")). | ||
setFlag("no-password", ""). | ||
setFlag("insecure", ""). | ||
run() | ||
|
||
NewCLICommand(). | ||
setCommand(fmt.Sprintf("../bin/step certificate create foo %s %s", temp("foo.crt"), temp("foo.key"))). | ||
setFlag("profile", "leaf"). | ||
setFlag("ca", temp("intermediate-ca.crt")). | ||
setFlag("ca-key", temp("intermediate-ca.key")). | ||
setFlag("no-password", ""). | ||
setFlag("insecure", ""). | ||
run() | ||
} | ||
|
||
func temp(filename string) string { | ||
return fmt.Sprintf("%s/%s", TempDirectory, filename) | ||
} |