Skip to content

Commit

Permalink
Merge pull request #58 from krancour/certs
Browse files Browse the repository at this point in the history
add cert-generation functions
  • Loading branch information
technosophos authored Oct 6, 2017
2 parents 4c16495 + 9641f52 commit efda631
Show file tree
Hide file tree
Showing 4 changed files with 421 additions and 4 deletions.
232 changes: 232 additions & 0 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import (
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net"
"time"

uuid "github.com/satori/go.uuid"
"golang.org/x/crypto/scrypt"
Expand Down Expand Up @@ -146,3 +150,231 @@ func pemBlockForKey(priv interface{}) *pem.Block {
return nil
}
}

type certificate struct {
Cert string
Key string
}

func generateCertificateAuthority(
cn string,
daysValid int,
) (certificate, error) {
ca := certificate{}

template, err := getBaseCertTemplate(cn, nil, nil, daysValid)
if err != nil {
return ca, err
}
// Override KeyUsage and IsCA
template.KeyUsage = x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature |
x509.KeyUsageCertSign
template.IsCA = true

priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return ca, fmt.Errorf("error generating rsa key: %s", err)
}

ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv)
if err != nil {
return ca, err
}

return ca, nil
}

func generateSelfSignedCertificate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
) (certificate, error) {
cert := certificate{}

template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
if err != nil {
return cert, err
}

priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return cert, fmt.Errorf("error generating rsa key: %s", err)
}

cert.Cert, cert.Key, err = getCertAndKey(template, priv, template, priv)
if err != nil {
return cert, err
}

return cert, nil
}

func generateSignedCertificate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
ca certificate,
) (certificate, error) {
cert := certificate{}

decodedSignerCert, _ := pem.Decode([]byte(ca.Cert))
if decodedSignerCert == nil {
return cert, errors.New("unable to decode certificate")
}
signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes)
if err != nil {
return cert, fmt.Errorf(
"error parsing certificate: decodedSignerCert.Bytes: %s",
err,
)
}
decodedSignerKey, _ := pem.Decode([]byte(ca.Key))
if decodedSignerKey == nil {
return cert, errors.New("unable to decode key")
}
signerKey, err := x509.ParsePKCS1PrivateKey(decodedSignerKey.Bytes)
if err != nil {
return cert, fmt.Errorf(
"error parsing prive key: decodedSignerKey.Bytes: %s",
err,
)
}

template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
if err != nil {
return cert, err
}

priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return cert, fmt.Errorf("error generating rsa key: %s", err)
}

cert.Cert, cert.Key, err = getCertAndKey(
template,
priv,
signerCert,
signerKey,
)
if err != nil {
return cert, err
}

return cert, nil
}

func getCertAndKey(
template *x509.Certificate,
signeeKey *rsa.PrivateKey,
parent *x509.Certificate,
signingKey *rsa.PrivateKey,
) (string, string, error) {
derBytes, err := x509.CreateCertificate(
rand.Reader,
template,
parent,
&signeeKey.PublicKey,
signingKey,
)
if err != nil {
return "", "", fmt.Errorf("error creating certificate: %s", err)
}

certBuffer := bytes.Buffer{}
if err := pem.Encode(
&certBuffer,
&pem.Block{Type: "CERTIFICATE", Bytes: derBytes},
); err != nil {
return "", "", fmt.Errorf("error pem-encoding certificate: %s", err)
}

keyBuffer := bytes.Buffer{}
if err := pem.Encode(
&keyBuffer,
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(signeeKey),
},
); err != nil {
return "", "", fmt.Errorf("error pem-encoding key: %s", err)
}

return string(certBuffer.Bytes()), string(keyBuffer.Bytes()), nil
}

func getBaseCertTemplate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
) (*x509.Certificate, error) {
ipAddresses, err := getNetIPs(ips)
if err != nil {
return nil, err
}
dnsNames, err := getAlternateDNSStrs(alternateDNS)
if err != nil {
return nil, err
}
return &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: cn,
},
IPAddresses: ipAddresses,
DNSNames: dnsNames,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}, nil
}

func getNetIPs(ips []interface{}) ([]net.IP, error) {
if ips == nil {
return []net.IP{}, nil
}
var ipStr string
var ok bool
var netIP net.IP
netIPs := make([]net.IP, len(ips))
for i, ip := range ips {
ipStr, ok = ip.(string)
if !ok {
return nil, fmt.Errorf("error parsing ip: %v is not a string", ip)
}
netIP = net.ParseIP(ipStr)
if netIP == nil {
return nil, fmt.Errorf("error parsing ip: %s", ipStr)
}
netIPs[i] = netIP
}
return netIPs, nil
}

func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) {
if alternateDNS == nil {
return []string{}, nil
}
var dnsStr string
var ok bool
alternateDNSStrs := make([]string, len(alternateDNS))
for i, dns := range alternateDNS {
dnsStr, ok = dns.(string)
if !ok {
return nil, fmt.Errorf(
"error processing alternate dns name: %v is not a string",
dns,
)
}
alternateDNSStrs[i] = dnsStr
}
return alternateDNSStrs, nil
}
117 changes: 117 additions & 0 deletions crypto_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package sprig

import (
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

const (
beginCertificate = "-----BEGIN CERTIFICATE-----"
endCertificate = "-----END CERTIFICATE-----"
)

func TestSha256Sum(t *testing.T) {
Expand Down Expand Up @@ -108,3 +118,110 @@ func TestUUIDGeneration(t *testing.T) {
t.Error("Expected subsequent UUID generations to be different")
}
}

func TestGenCA(t *testing.T) {
const cn = "foo-ca"

tpl := fmt.Sprintf(
`{{- $ca := genCA "%s" 365 }}
{{ $ca.Cert }}
`,
cn,
)
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
assert.Contains(t, out, beginCertificate)
assert.Contains(t, out, endCertificate)

decodedCert, _ := pem.Decode([]byte(out))
assert.Nil(t, err)
cert, err := x509.ParseCertificate(decodedCert.Bytes)
assert.Nil(t, err)

assert.Equal(t, cn, cert.Subject.CommonName)
assert.True(t, cert.IsCA)
}

func TestGenSelfSignedCert(t *testing.T) {
const (
cn = "foo.com"
ip1 = "10.0.0.1"
ip2 = "10.0.0.2"
dns1 = "bar.com"
dns2 = "bat.com"
)

tpl := fmt.Sprintf(
`{{- $cert := genSelfSignedCert "%s" (list "%s" "%s") (list "%s" "%s") 365 }}
{{ $cert.Cert }}`,
cn,
ip1,
ip2,
dns1,
dns2,
)

out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
assert.Contains(t, out, beginCertificate)
assert.Contains(t, out, endCertificate)

decodedCert, _ := pem.Decode([]byte(out))
assert.Nil(t, err)
cert, err := x509.ParseCertificate(decodedCert.Bytes)
assert.Nil(t, err)

assert.Equal(t, cn, cert.Subject.CommonName)
assert.Equal(t, 2, len(cert.IPAddresses))
assert.Equal(t, ip1, cert.IPAddresses[0].String())
assert.Equal(t, ip2, cert.IPAddresses[1].String())
assert.Contains(t, cert.DNSNames, dns1)
assert.Contains(t, cert.DNSNames, dns2)
assert.False(t, cert.IsCA)
}

func TestGenSignedCert(t *testing.T) {
const (
cn = "foo.com"
ip1 = "10.0.0.1"
ip2 = "10.0.0.2"
dns1 = "bar.com"
dns2 = "bat.com"
)

tpl := fmt.Sprintf(
`{{- $ca := genCA "foo" 365 }}
{{- $cert := genSignedCert "%s" (list "%s" "%s") (list "%s" "%s") 365 $ca }}
{{ $cert.Cert }}
`,
cn,
ip1,
ip2,
dns1,
dns2,
)
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}

assert.Contains(t, out, beginCertificate)
assert.Contains(t, out, endCertificate)

decodedCert, _ := pem.Decode([]byte(out))
assert.Nil(t, err)
cert, err := x509.ParseCertificate(decodedCert.Bytes)
assert.Nil(t, err)

assert.Equal(t, cn, cert.Subject.CommonName)
assert.Equal(t, 2, len(cert.IPAddresses))
assert.Equal(t, ip1, cert.IPAddresses[0].String())
assert.Equal(t, ip2, cert.IPAddresses[1].String())
assert.Contains(t, cert.DNSNames, dns1)
assert.Contains(t, cert.DNSNames, dns2)
assert.False(t, cert.IsCA)
}
Loading

0 comments on commit efda631

Please sign in to comment.