Skip to content

Commit a0ea4ed

Browse files
author
Zach Brown
committed
Reworked autocert to it's own repo and made later customization easier
1 parent f952c95 commit a0ea4ed

File tree

5 files changed

+202
-130
lines changed

5 files changed

+202
-130
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ It includes parsers for taking strings/config options and enabling minimum TLS v
88
It also includes some decent static defaults which can be used when creating a server.
99
```
1010
import (
11+
"github.com/snowzach/certtools/autocert"
1112
"github.com/snowzach/certtools"
1213
)
1314
15+
// Generate a self-signed certificate for localhost using static string as private key data (repeatable)
16+
// This programatically generates the same certificate every time and is only for development use
17+
cert, err = autocert.New(autocert.InsecureStringReader("static string"))
18+
19+
// Build an http server using our self-signed cert and decent security ciphers and versions
1420
server := &http.Server{
1521
Addr: ":8443",
1622
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

autocert.go

Lines changed: 0 additions & 128 deletions
This file was deleted.

autocert/autocert.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package autocert
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/tls"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
9+
"encoding/pem"
10+
"fmt"
11+
"io"
12+
"math/big"
13+
"net"
14+
"net/url"
15+
"time"
16+
)
17+
18+
type AutoCertOption func(*x509.Certificate)
19+
20+
// AutoCert generates a self-signed cert using the specified keyReader for a source for private key generation
21+
func New(keyReader io.Reader, opts ...AutoCertOption) (tls.Certificate, error) {
22+
23+
// Generate the key
24+
privKey, err := ecdsa.GenerateKey(elliptic.P256(), keyReader)
25+
if err != nil {
26+
return tls.Certificate{}, fmt.Errorf("Could not generate private key: %v\n", err)
27+
}
28+
29+
// Build Cert
30+
cert := x509.Certificate{
31+
SerialNumber: big.NewInt(0),
32+
Subject: pkix.Name{
33+
CommonName: "localhost",
34+
},
35+
36+
// Starting jan 1, 2010 for 100 years
37+
NotBefore: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
38+
NotAfter: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC).Add(100 * 365 * 24 * time.Hour),
39+
40+
IsCA: true,
41+
42+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
43+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
44+
45+
BasicConstraintsValid: true,
46+
}
47+
48+
// Apply the options
49+
for _, f := range opts {
50+
f(&cert)
51+
}
52+
53+
// Create Cert
54+
derBytes, err := x509.CreateCertificate(keyReader, &cert, &cert, &privKey.PublicKey, privKey)
55+
if err != nil {
56+
return tls.Certificate{}, fmt.Errorf("Failed to create certificate: %s", err)
57+
}
58+
59+
pKeyBytes, err := x509.MarshalECPrivateKey(privKey)
60+
if err != nil {
61+
return tls.Certificate{}, fmt.Errorf("Failed to marshal private key: %s", err)
62+
}
63+
64+
certBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
65+
privBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: pKeyBytes})
66+
67+
tlsCert, err := tls.X509KeyPair(certBytes, privBytes)
68+
if err != nil {
69+
return tls.Certificate{}, fmt.Errorf("Failed to load key-pair: %s", err)
70+
}
71+
72+
return tlsCert, nil
73+
}
74+
75+
// SerialNummber sets the serial number for the cert
76+
func SerialNumber(sn int64) AutoCertOption {
77+
return func(c *x509.Certificate) {
78+
c.SerialNumber = big.NewInt(sn)
79+
}
80+
}
81+
82+
// CommonName sets the commont name of the certificate
83+
func CommonName(cn string) AutoCertOption {
84+
return func(c *x509.Certificate) {
85+
c.Subject = pkix.Name{
86+
CommonName: cn,
87+
}
88+
}
89+
}
90+
91+
// Organization sets the Organization(s) of the cert subject
92+
func Organization(o []string) AutoCertOption {
93+
return func(c *x509.Certificate) {
94+
c.Subject.Organization = o
95+
}
96+
}
97+
98+
// OrganizationalUnit sets the OrganizationalUnit(s) of the cert subject
99+
func OrganizationalUnit(ou []string) AutoCertOption {
100+
return func(c *x509.Certificate) {
101+
c.Subject.OrganizationalUnit = ou
102+
}
103+
}
104+
105+
// DNSNames sets the DNS names of the cert
106+
func DNSNames(dnsNames []string) AutoCertOption {
107+
return func(c *x509.Certificate) {
108+
c.DNSNames = dnsNames
109+
}
110+
}
111+
112+
// URIs sets the URIs of the cert
113+
func URIs(uris []*url.URL) AutoCertOption {
114+
return func(c *x509.Certificate) {
115+
c.URIs = uris
116+
}
117+
}
118+
119+
// IPAddresses sets the IPAddresses of the cert
120+
func IPAddresses(ips []net.IP) AutoCertOption {
121+
return func(c *x509.Certificate) {
122+
c.IPAddresses = ips
123+
}
124+
}
125+
126+
// ValidTimes sets the times in which this cert is valid
127+
func ValidTimes(notBefore time.Time, notAfter time.Time) AutoCertOption {
128+
return func(c *x509.Certificate) {
129+
c.NotBefore = notBefore
130+
c.NotAfter = notAfter
131+
}
132+
}

autocert/certreaders.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package autocert
2+
3+
// InsecureGlobalStatic is a non-random byte reader that can be used to generaate an insecure private key
4+
// This will generate the same bytes on every box (all zeros). It is horribly insecure.
5+
type InsecureGlobalStatic struct{}
6+
7+
func InsecureGlobalStaticReader() InsecureGlobalStatic {
8+
return InsecureGlobalStatic{}
9+
}
10+
11+
func (r InsecureGlobalStatic) Read(s []byte) (int, error) {
12+
// Set it to all zeros
13+
l := len(s)
14+
for x := 0; x < l; x++ {
15+
s[x] = 0
16+
}
17+
return l, nil
18+
}
19+
20+
// InsecureString is a non-random bytes reader that can be used to generate an insecure private key based on a provided string
21+
// The upside of this is that the same string input should yield the same bytes so you can send in something like the hostname
22+
// and it will generate the same output everytime you run your program.
23+
// The downside is that it is very insecure and should only be used for testing
24+
type InsecureString struct {
25+
seed []byte
26+
pos int
27+
length int
28+
}
29+
30+
func InsecureStringReader(seed string) *InsecureString {
31+
// Ensure there is at least one character in seed
32+
if len(seed) == 0 {
33+
seed = " "
34+
}
35+
return &InsecureString{
36+
seed: []byte(seed),
37+
pos: 0,
38+
length: len(seed),
39+
}
40+
}
41+
func (r *InsecureString) Read(s []byte) (int, error) {
42+
// Just repead the string over and over
43+
l := len(s)
44+
for x := 0; x < l; x++ {
45+
s[x] = r.seed[r.pos%r.length]
46+
r.pos++
47+
}
48+
return l, nil
49+
}

example/example.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import (
66
"fmt"
77
"net"
88
"net/http"
9+
"net/url"
910
"os"
1011
"time"
1112

1213
"github.com/snowzach/certtools"
14+
"github.com/snowzach/certtools/autocert"
1315
)
1416

1517
func main() {
@@ -24,13 +26,24 @@ func main() {
2426
cn := flag.String("cn", hostname, "The common name for the certificate")
2527
o := flag.String("o", "", "The org for the certificate")
2628
ou := flag.String("ou", "", "The org unit for the certificate")
29+
flag.Parse()
30+
31+
uri, _ := url.Parse("https://" + hostname)
2732

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

3237
// This will generate the same certificate every time it is run on the same host
33-
cert, err := certtools.AutoCert(*cn, *o, *ou, nil, notBefore, notAfter, certtools.InsecureStringReader(hostname))
38+
cert, err := autocert.New(autocert.InsecureStringReader(hostname),
39+
autocert.SerialNumber(1),
40+
autocert.CommonName(*cn),
41+
autocert.Organization([]string{*o}),
42+
autocert.OrganizationalUnit([]string{*ou}),
43+
autocert.URIs([]*url.URL{uri}),
44+
autocert.DNSNames([]string{hostname}),
45+
autocert.ValidTimes(notBefore, notAfter),
46+
)
3447

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

0 commit comments

Comments
 (0)