From 5be8a8a39c4dfe4f6c1a84b3c8d1da86bfbade0c Mon Sep 17 00:00:00 2001 From: Yoon Ho Han Date: Sat, 10 Oct 2015 15:49:16 -0700 Subject: [PATCH] add pkcs#12 feature added ability to output a pkcs#12 file using CLI and API --- Godeps/Godeps.json | 6 ++++ api/generator/generator.go | 17 +++++++++++ cli/cli.go | 26 +++++++++++++++++ cli/config.go | 2 ++ cli/gencert/gencert.go | 31 +++++++++++++++++--- crypto/pkcs12/pkcs12.go | 55 ++++++++++++++++++++++++++++++++++++ doc/api/endpoint_newcert.txt | 2 ++ 7 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 crypto/pkcs12/pkcs12.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 6cc11e27c..92f6b61cc 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -27,6 +27,12 @@ "Comment": "v2-6-gba14da1", "Rev": "ba14da1f827188454a4591717fff29999010887f" }, + { + + "ImportPath": "github.com/AGWA-forks/go-pkcs12", + "Rev": "800c1a19d11c8a89298008a8321f9700ba2f0e06" + + }, { "ImportPath": "github.com/cloudflare/cf-tls/tls", "Rev": "28cbf4e4c1f859789a26b308eb8b684f34f38312" diff --git a/api/generator/generator.go b/api/generator/generator.go index b4eaf6b4e..9f1fe4dd5 100644 --- a/api/generator/generator.go +++ b/api/generator/generator.go @@ -13,6 +13,7 @@ import ( "github.com/cloudflare/cfssl/api" "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/crypto/pkcs12" "github.com/cloudflare/cfssl/csr" "github.com/cloudflare/cfssl/errors" "github.com/cloudflare/cfssl/log" @@ -203,6 +204,12 @@ type genSignRequest struct { Request *csr.CertificateRequest `json:"request"` Profile string `json:"profile"` Label string `json:"label"` + Format format `json:"format"` +} + +type format struct { + Type string `json:"type"` + Password string `json:"password"` } // Handle responds to requests for the CA to generate a new private @@ -265,10 +272,20 @@ func (cg *CertGeneratorHandler) Handle(w http.ResponseWriter, r *http.Request) e return errors.NewBadRequest(err) } + var file string + if req.Format.Type == "pkcs12" { + var password []byte + if req.Format.Password != "" { + password = []byte(req.Format.Password) + } + file = pkcs12.ParseAndEncode(key, certBytes, password) + } + result := map[string]interface{}{ "private_key": string(key), "certificate_request": string(csr), "certificate": string(certBytes), + "format": file, "sums": map[string]Sum{ "certificate_request": reqSum, "certificate": certSum, diff --git a/cli/cli.go b/cli/cli.go index 3016f1cf7..f4df07905 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -173,6 +173,32 @@ func PrintCert(key, csrBytes, cert []byte) { fmt.Printf("%s\n", jsonOut) } +// PrintCertAndFile outputs a cert, key, csr, and file to stdout +func PrintCertAndFile(key, csrBytes, cert []byte, file string) { + out := map[string]string{} + if cert != nil { + out["cert"] = string(cert) + } + + if key != nil { + out["key"] = string(key) + } + + if csrBytes != nil { + out["csr"] = string(csrBytes) + } + + if file != "" { + out["file"] = file + } + + jsonOut, err := json.Marshal(out) + if err != nil { + return + } + fmt.Printf("%s\n", jsonOut) +} + // PrintOCSPResponse outputs an OCSP response to stdout // ocspResponse is base64 encoded func PrintOCSPResponse(resp []byte) { diff --git a/cli/config.go b/cli/config.go index c4a54eec1..2a3ef7bd4 100644 --- a/cli/config.go +++ b/cli/config.go @@ -24,6 +24,7 @@ type Config struct { IntermediatesFile string CABundleFile string IntBundleFile string + Format string Address string Port int Password string @@ -73,6 +74,7 @@ func registerFlags(c *Config, f *flag.FlagSet) { f.StringVar(&c.IntermediatesFile, "intermediates", "", "intermediate certs") f.StringVar(&c.CABundleFile, "ca-bundle", "", "path to root certificate store") f.StringVar(&c.IntBundleFile, "int-bundle", "", "path to intermediate certificate store") + f.StringVar(&c.Format, "format", "", "format for the exported file") f.StringVar(&c.Address, "address", "127.0.0.1", "Address to bind") f.IntVar(&c.Port, "port", 8888, "Port to bind") f.StringVar(&c.ConfigFile, "config", "", "path to configuration file") diff --git a/cli/gencert/gencert.go b/cli/gencert/gencert.go index 977a38b3d..0611a2685 100644 --- a/cli/gencert/gencert.go +++ b/cli/gencert/gencert.go @@ -9,13 +9,14 @@ import ( "github.com/cloudflare/cfssl/cli" "github.com/cloudflare/cfssl/cli/genkey" "github.com/cloudflare/cfssl/cli/sign" + "github.com/cloudflare/cfssl/crypto/pkcs12" "github.com/cloudflare/cfssl/csr" "github.com/cloudflare/cfssl/initca" "github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/signer" ) -var gencertUsageText = `cfssl gencert -- generate a new key and signed certificate +var gencertUsageText = `cfssl gencert -- generate a new key and signed certificate (or PKCS#12 bundle) Usage of gencert: Generate a new key and cert from CSR: @@ -29,13 +30,17 @@ Usage of gencert: Re-generate a CA cert with the CA key and certificate: cfssl gencert -renewca -ca cert -ca-key key + Generate a PKCS#12 file with a new key and cert from CSR: + cfssl gencert -initca -format pkcs12 [-password password] CSRJSON + cfssl gencert -ca cert -ca-key key -format pkcs12 [-password password] CSRJSON + Arguments: CSRJSON: JSON file containing the request, use '-' for reading JSON from stdin Flags: ` -var gencertFlags = []string{"initca", "remote", "ca", "ca-key", "config", "hostname", "profile", "label"} +var gencertFlags = []string{"initca", "remote", "ca", "ca-key", "config", "hostname", "profile", "label", "format", "password"} func gencertMain(args []string, c cli.Config) error { if c.RenewCA { @@ -84,7 +89,16 @@ func gencertMain(args []string, c cli.Config) error { } } - cli.PrintCert(key, csrPEM, cert) + if c.Format == "pkcs12" { + var password []byte + if c.Password != "0" { + password = []byte(c.Password) + } + file := pkcs12.ParseAndEncode(key, cert, password) + cli.PrintCertAndFile(key, csrPEM, cert, file) + } else { + cli.PrintCert(key, csrPEM, cert) + } default: if req.CA != nil { @@ -143,7 +157,16 @@ func gencertMain(args []string, c cli.Config) error { log.Warning(generator.CSRNoHostMessage) } - cli.PrintCert(key, csrBytes, cert) + if c.Format == "pkcs12" { + var password []byte + if c.Password != "0" { + password = []byte(c.Password) + } + file := pkcs12.ParseAndEncode(key, cert, password) + cli.PrintCertAndFile(key, csrBytes, cert, file) + } else { + cli.PrintCert(key, csrBytes, cert) + } } return nil } diff --git a/crypto/pkcs12/pkcs12.go b/crypto/pkcs12/pkcs12.go new file mode 100644 index 000000000..a84189f67 --- /dev/null +++ b/crypto/pkcs12/pkcs12.go @@ -0,0 +1,55 @@ +// Package pkcs12 implements the parsing and encoding of key and certificate files into a PKCS#12 file +package pkcs12 + +import ( + "crypto/x509" + "encoding/base64" + "encoding/pem" + + "github.com/AGWA-forks/go-pkcs12" +) + +// ParseAndEncode takes key, certificate, and optional password +// as []byte and parses them to get a suitable format to encode them +func ParseAndEncode(key, cert, password []byte) string { + var file string + certBlock, _ := pem.Decode(cert) + certBytes := certBlock.Bytes + certificate, err := x509.ParseCertificate(certBytes) + if err != nil { + return file + } + + block, _ := pem.Decode(key) + privKey := block.Bytes + if block.Type == "RSA PRIVATE KEY" { + pkcsKey, err := x509.ParsePKCS1PrivateKey(privKey) + if err != nil { + return file + } + file = Encode(pkcsKey, certificate, password) + } else if block.Type == "EC PRIVATE KEY" { + ecKey, err := x509.ParseECPrivateKey(privKey) + if err != nil { + return file + } + file = Encode(ecKey, certificate, password) + } + + return file +} + +// Encode is called by ParseAndEncode with a key, certificate, and optional password +// to call AGWA-forks's pkcs12 encode function and returns the pkcs12 file as a base64 encoded string +func Encode(privateKey interface{}, certificate *x509.Certificate, password []byte) string { + var none []*x509.Certificate + var data string + pfxData, err := pkcs12.Encode(privateKey, certificate, none, password) + if err != nil { + return data + } + + data = base64.StdEncoding.EncodeToString(pfxData) + + return data +} diff --git a/doc/api/endpoint_newcert.txt b/doc/api/endpoint_newcert.txt index 0d3f0b4a7..9d01963a7 100644 --- a/doc/api/endpoint_newcert.txt +++ b/doc/api/endpoint_newcert.txt @@ -16,6 +16,8 @@ Optional parameters: the CSR, useful when interacting with cfssl server that stands in front of a remote multi-root CA signer * profile: a string specifying the signing profile for the signer + * format: a json object specifying the output file type and + optional password. (example: "format": {"type": "pkcs12", "password": "password"}) Result: