Added pki which is a fork of https://github.com/mastahyeti/fakeca needed for testing.
This commit is contained in:
parent
6604a9bc8c
commit
d4389bbcc5
368
pki/pki.go
Normal file
368
pki/pki.go
Normal file
@ -0,0 +1,368 @@
|
||||
//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
|
||||
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(),
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
51
pki/pki_test.go
Normal file
51
pki/pki_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pki "github.com/InfiniteLoopSpace/go_S-MIME/pki"
|
||||
)
|
||||
|
||||
func TestCA(t *testing.T) {
|
||||
|
||||
pki.DefaultProvince = []string{"CO"}
|
||||
pki.DefaultLocality = []string{"Denver"}
|
||||
|
||||
// Create a root CA.
|
||||
root := pki.New(pki.IsCA, pki.Subject(pkix.Name{
|
||||
CommonName: "root.myorg.com",
|
||||
}))
|
||||
|
||||
// Create an intermediate CA under the root.
|
||||
intermediate := root.Issue(pki.IsCA, pki.Subject(pkix.Name{
|
||||
CommonName: "intermediate.myorg.com",
|
||||
}))
|
||||
|
||||
// Create a leaf certificate under the intermediate.
|
||||
leaf := intermediate.Issue(pki.Subject(pkix.Name{
|
||||
CommonName: "leaf.myorg.com",
|
||||
}))
|
||||
|
||||
Intermediate := x509.NewCertPool()
|
||||
Intermediate.AddCert(intermediate.Certificate)
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
roots.AddCert(root.Certificate)
|
||||
|
||||
Opts := x509.VerifyOptions{
|
||||
Intermediates: Intermediate,
|
||||
Roots: roots,
|
||||
CurrentTime: time.Now(),
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||
}
|
||||
|
||||
_, err := leaf.Certificate.Verify(Opts)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user