Skip to content

Commit

Permalink
Reworked autocert to it's own repo and made later customization easier
Browse files Browse the repository at this point in the history
  • Loading branch information
Zach Brown committed Dec 1, 2018
1 parent f952c95 commit a0ea4ed
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 130 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ It includes parsers for taking strings/config options and enabling minimum TLS v
It also includes some decent static defaults which can be used when creating a server.
```
import (
"github.com/snowzach/certtools/autocert"
"github.com/snowzach/certtools"
)
// Generate a self-signed certificate for localhost using static string as private key data (repeatable)
// This programatically generates the same certificate every time and is only for development use
cert, err = autocert.New(autocert.InsecureStringReader("static string"))
// Build an http server using our self-signed cert and decent security ciphers and versions
server := &http.Server{
Addr: ":8443",
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
Expand Down
128 changes: 0 additions & 128 deletions autocert.go

This file was deleted.

132 changes: 132 additions & 0 deletions autocert/autocert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package autocert

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/url"
"time"
)

type AutoCertOption func(*x509.Certificate)

// AutoCert generates a self-signed cert using the specified keyReader for a source for private key generation
func New(keyReader io.Reader, opts ...AutoCertOption) (tls.Certificate, error) {

// Generate the key
privKey, err := ecdsa.GenerateKey(elliptic.P256(), keyReader)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Could not generate private key: %v\n", err)
}

// Build Cert
cert := x509.Certificate{
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: "localhost",
},

// Starting jan 1, 2010 for 100 years
NotBefore: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
NotAfter: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC).Add(100 * 365 * 24 * time.Hour),

IsCA: true,

ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,

BasicConstraintsValid: true,
}

// Apply the options
for _, f := range opts {
f(&cert)
}

// Create Cert
derBytes, err := x509.CreateCertificate(keyReader, &cert, &cert, &privKey.PublicKey, privKey)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Failed to create certificate: %s", err)
}

pKeyBytes, err := x509.MarshalECPrivateKey(privKey)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Failed to marshal private key: %s", err)
}

certBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
privBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: pKeyBytes})

tlsCert, err := tls.X509KeyPair(certBytes, privBytes)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Failed to load key-pair: %s", err)
}

return tlsCert, nil
}

// SerialNummber sets the serial number for the cert
func SerialNumber(sn int64) AutoCertOption {
return func(c *x509.Certificate) {
c.SerialNumber = big.NewInt(sn)
}
}

// CommonName sets the commont name of the certificate
func CommonName(cn string) AutoCertOption {
return func(c *x509.Certificate) {
c.Subject = pkix.Name{
CommonName: cn,
}
}
}

// Organization sets the Organization(s) of the cert subject
func Organization(o []string) AutoCertOption {
return func(c *x509.Certificate) {
c.Subject.Organization = o
}
}

// OrganizationalUnit sets the OrganizationalUnit(s) of the cert subject
func OrganizationalUnit(ou []string) AutoCertOption {
return func(c *x509.Certificate) {
c.Subject.OrganizationalUnit = ou
}
}

// DNSNames sets the DNS names of the cert
func DNSNames(dnsNames []string) AutoCertOption {
return func(c *x509.Certificate) {
c.DNSNames = dnsNames
}
}

// URIs sets the URIs of the cert
func URIs(uris []*url.URL) AutoCertOption {
return func(c *x509.Certificate) {
c.URIs = uris
}
}

// IPAddresses sets the IPAddresses of the cert
func IPAddresses(ips []net.IP) AutoCertOption {
return func(c *x509.Certificate) {
c.IPAddresses = ips
}
}

// ValidTimes sets the times in which this cert is valid
func ValidTimes(notBefore time.Time, notAfter time.Time) AutoCertOption {
return func(c *x509.Certificate) {
c.NotBefore = notBefore
c.NotAfter = notAfter
}
}
49 changes: 49 additions & 0 deletions autocert/certreaders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package autocert

// InsecureGlobalStatic is a non-random byte reader that can be used to generaate an insecure private key
// This will generate the same bytes on every box (all zeros). It is horribly insecure.
type InsecureGlobalStatic struct{}

func InsecureGlobalStaticReader() InsecureGlobalStatic {
return InsecureGlobalStatic{}
}

func (r InsecureGlobalStatic) Read(s []byte) (int, error) {
// Set it to all zeros
l := len(s)
for x := 0; x < l; x++ {
s[x] = 0
}
return l, nil
}

// InsecureString is a non-random bytes reader that can be used to generate an insecure private key based on a provided string
// The upside of this is that the same string input should yield the same bytes so you can send in something like the hostname
// and it will generate the same output everytime you run your program.
// The downside is that it is very insecure and should only be used for testing
type InsecureString struct {
seed []byte
pos int
length int
}

func InsecureStringReader(seed string) *InsecureString {
// Ensure there is at least one character in seed
if len(seed) == 0 {
seed = " "
}
return &InsecureString{
seed: []byte(seed),
pos: 0,
length: len(seed),
}
}
func (r *InsecureString) Read(s []byte) (int, error) {
// Just repead the string over and over
l := len(s)
for x := 0; x < l; x++ {
s[x] = r.seed[r.pos%r.length]
r.pos++
}
return l, nil
}
17 changes: 15 additions & 2 deletions example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"fmt"
"net"
"net/http"
"net/url"
"os"
"time"

"github.com/snowzach/certtools"
"github.com/snowzach/certtools/autocert"
)

func main() {
Expand All @@ -24,13 +26,24 @@ func main() {
cn := flag.String("cn", hostname, "The common name for the certificate")
o := flag.String("o", "", "The org for the certificate")
ou := flag.String("ou", "", "The org unit for the certificate")
flag.Parse()

uri, _ := url.Parse("https://" + hostname)

// Good starting at unix epoch for 100 years
var notBefore time.Time
var notBefore = time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC)
var notAfter time.Time = notBefore.Add(100 * 365 * 24 * time.Hour)

// This will generate the same certificate every time it is run on the same host
cert, err := certtools.AutoCert(*cn, *o, *ou, nil, notBefore, notAfter, certtools.InsecureStringReader(hostname))
cert, err := autocert.New(autocert.InsecureStringReader(hostname),
autocert.SerialNumber(1),
autocert.CommonName(*cn),
autocert.Organization([]string{*o}),
autocert.OrganizationalUnit([]string{*ou}),
autocert.URIs([]*url.URL{uri}),
autocert.DNSNames([]string{hostname}),
autocert.ValidTimes(notBefore, notAfter),
)

// Build the server and manually specify TLS Config
server := &http.Server{
Expand Down

0 comments on commit a0ea4ed

Please sign in to comment.