go_S-MIME/pki/pki.go
2018-12-21 14:43:59 +01:00

378 lines
8.1 KiB
Go

//Package pki can create ca's intermediates and certificates
package pki
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math"
"math/big"
"os/exec"
"time"
)
type configuration struct {
subject *pkix.Name
issuer *Identity
nextSN *int64
signatureAlgrotim x509.SignatureAlgorithm
priv *crypto.Signer
isCA bool
notBefore *time.Time
notAfter *time.Time
issuingCertificateURL []string
ocspServer []string
}
func (c *configuration) generate() *Identity {
templ := &x509.Certificate{
Subject: c.getSubject(),
SignatureAlgorithm: c.signatureAlgrotim,
IsCA: c.isCA,
BasicConstraintsValid: true,
NotAfter: c.getNotAfter(),
NotBefore: c.getNotBefore(),
IssuingCertificateURL: c.issuingCertificateURL,
OCSPServer: c.ocspServer,
}
var (
parent *x509.Certificate
thisPriv = c.getPrivateKey()
priv crypto.Signer
)
if c.issuer != nil {
parent = c.issuer.Certificate
templ.SerialNumber = big.NewInt(c.issuer.IncrementSN())
priv = c.issuer.PrivateKey
} else {
parent = templ
templ.SerialNumber = randSN()
priv = thisPriv
}
der, err := x509.CreateCertificate(rand.Reader, templ, parent, thisPriv.Public(), priv)
if err != nil {
panic(err)
}
cert, err := x509.ParseCertificate(der)
if err != nil {
panic(err)
}
return &Identity{
Certificate: cert,
PrivateKey: thisPriv,
Issuer: c.issuer,
NextSN: c.getNextSN(),
}
}
var (
// DefaultCountry is the default subject Country.
DefaultCountry = []string{"US"}
// DefaultProvince is the default subject Province.
DefaultProvince = []string{"CA"}
// DefaultLocality is the default subject Locality.
DefaultLocality = []string{"San Francisco"}
// DefaultStreetAddress is the default subject StreetAddress.
DefaultStreetAddress = []string(nil)
// DefaultPostalCode is the default subject PostalCode.
DefaultPostalCode = []string(nil)
// DefaultCommonName is the default subject CommonName.
DefaultCommonName = "fakeca"
cnCounter int64
)
func (c *configuration) getSubject() pkix.Name {
if c.subject != nil {
return *c.subject
}
var cn string
if cnCounter == 0 {
cn = DefaultCommonName
} else {
cn = fmt.Sprintf("%s #%d", DefaultCommonName, cnCounter)
}
cnCounter++
return pkix.Name{
Country: DefaultCountry,
Province: DefaultProvince,
Locality: DefaultLocality,
StreetAddress: DefaultStreetAddress,
PostalCode: DefaultPostalCode,
CommonName: cn,
}
}
func (c *configuration) getNextSN() int64 {
if c.nextSN == nil {
sn := randSN().Int64()
c.nextSN = &sn
}
return *c.nextSN
}
func randSN() *big.Int {
i, err := rand.Int(rand.Reader, big.NewInt(int64(math.MaxInt64)))
if err != nil {
panic(err)
}
return i
}
func (c *configuration) getPrivateKey() crypto.Signer {
if c.priv == nil {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
signer := crypto.Signer(priv)
c.priv = &signer
}
return *c.priv
}
func (c *configuration) getNotBefore() time.Time {
if c.notBefore == nil {
return time.Unix(0, 0)
}
return *c.notBefore
}
func (c *configuration) getNotAfter() time.Time {
if c.notAfter == nil {
return time.Now().Add(time.Hour * 24 * 365 * 10)
}
return *c.notAfter
}
// Option is an option that can be passed to New().
type Option option
type option func(c *configuration)
// Subject is an Option that sets a identity's subject field.
func Subject(value pkix.Name) Option {
return func(c *configuration) {
c.subject = &value
}
}
// NextSerialNumber is an Option that determines the SN of the next issued
// certificate.
func NextSerialNumber(value int64) Option {
return func(c *configuration) {
c.nextSN = &value
}
}
// PrivateKey is an Option for setting the identity's private key.
func PrivateKey(value crypto.Signer) Option {
return func(c *configuration) {
c.priv = &value
}
}
// SignatureAlgorithm is an Option for setting the signature algorithm.
func SignatureAlgorithm(value x509.SignatureAlgorithm) Option {
return func(c *configuration) {
c.signatureAlgrotim = value
}
}
// Issuer is an Option for setting the identity's issuer.
func Issuer(value *Identity) Option {
return func(c *configuration) {
c.issuer = value
}
}
// NotBefore is an Option for setting the identity's certificate's NotBefore.
func NotBefore(value time.Time) Option {
return func(c *configuration) {
c.notBefore = &value
}
}
// NotAfter is an Option for setting the identity's certificate's NotAfter.
func NotAfter(value time.Time) Option {
return func(c *configuration) {
c.notAfter = &value
}
}
// IssuingCertificateURL is an Option for setting the identity's certificate's
// IssuingCertificateURL.
func IssuingCertificateURL(value ...string) Option {
return func(c *configuration) {
c.issuingCertificateURL = append(c.issuingCertificateURL, value...)
}
}
// OCSPServer is an Option for setting the identity's certificate's OCSPServer.
func OCSPServer(value ...string) Option {
return func(c *configuration) {
c.ocspServer = append(c.ocspServer, value...)
}
}
// IsCA is an Option for making an identity a certificate authority.
var IsCA Option = func(c *configuration) {
c.isCA = true
}
// 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()
}