1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
package certificate
import (
"crypto/rand"
"crypto/x509"
"github.com/pkg/errors"
"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 p12Command() cli.Command {
return cli.Command{
Name: "p12",
Action: command.ActionFunc(p12Action),
Usage: `package a certificate and keys into a .p12 file`,
UsageText: `step certificate p12 <p12-path> [<crt-path>] [<key-path>]
[**--ca**=<file>] [**--password-file**=<file>]`,
Description: `**step certificate p12** creates a .p12 (PFX / PKCS12)
file containing certificates and keys. This can then be used to import
into Windows / Firefox / Java applications.
## EXIT CODES
This command returns 0 on success and \>0 if any error occurs.
## EXAMPLES
Package a certificate and private key together:
'''
$ step certificate p12 foo.p12 foo.crt foo.key
'''
Package a certificate and private key together, and include an intermediate certificate:
'''
$ step certificate p12 foo.p12 foo.crt foo.key --ca intermediate.crt
'''
Package a CA certificate into a "trust store" for Java applications:
'''
$ step certificate p12 trust.p12 --ca ca.crt
'''
Package a certificate and private key with an empty password:
'''
$ step certificate p12 --no-password --insecure foo.p12 foo.crt foo.key
'''`,
Flags: []cli.Flag{
cli.StringSliceFlag{
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 encrypt the .p12 file.`,
},
flags.NoPassword,
flags.Force,
flags.Insecure,
},
}
}
func p12Action(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)
caFiles := ctx.StringSlice("ca")
hasKeyAndCert := crtFile != "" && keyFile != ""
//If either key or cert are provided, both must be provided
if !hasKeyAndCert && (crtFile != "" || keyFile != "") {
return errs.MissingArguments(ctx, "key_file")
}
//If no key and cert are provided, ca files must be provided
if !hasKeyAndCert && len(caFiles) == 0 {
return errors.Errorf("flag '--%s' must be provided when no <crt_path> and <key_path> are present", "ca")
}
// Validate flags
switch {
case ctx.String("password-file") != "" && ctx.Bool("no-password"):
return errs.IncompatibleFlagWithFlag(ctx, "no-password", "password-file")
case ctx.Bool("no-password") && !ctx.Bool("insecure"):
return errs.RequiredInsecureFlag(ctx, "no-password")
}
x509CAs := []*x509.Certificate{}
for _, caFile := range caFiles {
x509Bundle, err := pemutil.ReadCertificateBundle(caFile)
if err != nil {
return errors.Wrap(err, "error reading CA certificate")
}
x509CAs = append(x509CAs, x509Bundle...)
}
var err error
var password string
if !ctx.Bool("no-password") {
if passwordFile := ctx.String("password-file"); passwordFile != "" {
password, err = utils.ReadStringPasswordFromFile(passwordFile)
if err != nil {
return err
}
}
if password == "" {
pass, err := ui.PromptPassword("Please enter a password to encrypt the .p12 file")
if err != nil {
return errors.Wrap(err, "error reading password")
}
password = string(pass)
}
}
var pkcs12Data []byte
if hasKeyAndCert {
//If we have a key and certificate, we're making an identity store
x509CertBundle, err := pemutil.ReadCertificateBundle(crtFile)
if err != nil {
return errors.Wrap(err, "error reading certificate")
}
key, err := pemutil.Read(keyFile)
if err != nil {
return errors.Wrap(err, "error reading key")
}
//The first certificate in the bundle will be our server cert
x509Cert := x509CertBundle[0]
//Any remaning certs will be intermediates for the server
x509CAs = append(x509CAs, x509CertBundle[1:]...)
pkcs12Data, err = pkcs12.Encode(rand.Reader, key, x509Cert, x509CAs, password)
if err != nil {
return errs.Wrap(err, "failed to encode PKCS12 data")
}
} else {
//If we have only --ca flags, we're making a trust store
pkcs12Data, err = pkcs12.EncodeTrustStore(rand.Reader, x509CAs, password)
if err != nil {
return errs.Wrap(err, "failed to encode PKCS12 data")
}
}
if err := utils.WriteFile(p12File, pkcs12Data, 0600); err != nil {
return err
}
ui.Printf("Your .p12 bundle has been saved as %s.\n", p12File)
return nil
}
|