From d4389bbcc57652c70f2d8a8dc758be2aafb092ba Mon Sep 17 00:00:00 2001 From: InfiniteLoopSpace <35842605+InfiniteLoopSpace@users.noreply.github.com> Date: Wed, 14 Nov 2018 15:07:10 +0100 Subject: [PATCH] Added pki which is a fork of https://github.com/mastahyeti/fakeca needed for testing. --- pki/pki.go | 368 ++++++++++++++++++++++++++++++++++++++++++++++++ pki/pki_test.go | 51 +++++++ 2 files changed, 419 insertions(+) create mode 100644 pki/pki.go create mode 100644 pki/pki_test.go diff --git a/pki/pki.go b/pki/pki.go new file mode 100644 index 0000000..88a597c --- /dev/null +++ b/pki/pki.go @@ -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() +} diff --git a/pki/pki_test.go b/pki/pki_test.go new file mode 100644 index 0000000..cf98c3d --- /dev/null +++ b/pki/pki_test.go @@ -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) + } + +}