fakeca/identity.go (107 lines of code) (raw):
package fakeca
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os/exec"
)
// Identity is a certificate and private key.
type Identity struct {
Issuer *Identity
PrivateKey crypto.Signer
Certificate *x509.Certificate
NextSN int64
}
// New creates a new CA.
func New(opts ...Option) *Identity {
c := &configuration{}
for _, opt := range opts {
option(opt)(c)
}
return c.generate()
}
// Issue issues a new Identity with this one as its parent.
func (id *Identity) Issue(opts ...Option) *Identity {
opts = append(opts, Issuer(id))
return New(opts...)
}
// PFX wraps the certificate and private key in an encrypted PKCS#12 packet. The
// provided password must be alphanumeric.
func (id *Identity) PFX(password string) []byte {
return toPFX(id.Certificate, id.PrivateKey, password)
}
// Chain builds a slice of *x509.Certificate from this CA and its issuers.
func (id *Identity) Chain() []*x509.Certificate {
chain := []*x509.Certificate{}
for this := id; this != nil; this = this.Issuer {
chain = append(chain, this.Certificate)
}
return chain
}
// ChainPool builds an *x509.CertPool from this CA and its issuers.
func (id *Identity) ChainPool() *x509.CertPool {
chain := x509.NewCertPool()
for this := id; this != nil; this = this.Issuer {
chain.AddCert(this.Certificate)
}
return chain
}
// IncrementSN returns the next serial number.
func (id *Identity) IncrementSN() int64 {
defer func() {
id.NextSN++
}()
return id.NextSN
}
func toPFX(cert *x509.Certificate, priv interface{}, password string) []byte {
// only allow alphanumeric passwords
for _, c := range password {
switch {
case c >= 'a' && c <= 'z':
case c >= 'A' && c <= 'Z':
case c >= '0' && c <= '9':
default:
panic("password must be alphanumeric")
}
}
passout := fmt.Sprintf("pass:%s", password)
cmd := exec.Command("openssl", "pkcs12", "-export", "-passout", passout)
cmd.Stdin = bytes.NewReader(append(append(toPKCS8(priv), '\n'), toPEM(cert)...))
out := new(bytes.Buffer)
cmd.Stdout = out
if err := cmd.Run(); err != nil {
panic(err)
}
return out.Bytes()
}
func toPEM(cert *x509.Certificate) []byte {
buf := new(bytes.Buffer)
if err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
panic(err)
}
return buf.Bytes()
}
func toDER(priv interface{}) []byte {
var (
der []byte
err error
)
switch p := priv.(type) {
case *rsa.PrivateKey:
der = x509.MarshalPKCS1PrivateKey(p)
case *ecdsa.PrivateKey:
der, err = x509.MarshalECPrivateKey(p)
default:
err = errors.New("unknown key type")
}
if err != nil {
panic(err)
}
return der
}
func toPKCS8(priv interface{}) []byte {
cmd := exec.Command("openssl", "pkcs8", "-topk8", "-nocrypt", "-inform", "DER")
cmd.Stdin = bytes.NewReader(toDER(priv))
out := new(bytes.Buffer)
cmd.Stdout = out
if err := cmd.Run(); err != nil {
panic(err)
}
return out.Bytes()
}