certgen.go raw
1 package util
2
3 import (
4 "bytes"
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 _ "crypto/sha512" // Needed for RegisterHash in init
9 "crypto/x509"
10 "crypto/x509/pkix"
11 "encoding/pem"
12 "errors"
13 "fmt"
14 "math/big"
15 "net"
16 "os"
17 "time"
18 )
19
20 // NewTLSCertPair returns a new PEM-encoded x.509 certificate pair based on a 521-bit ECDSA private key. The machine's
21 // local interface addresses and all variants of IPv4 and IPv6 localhost are included as valid IP addresses.
22 func NewTLSCertPair(organization string, validUntil time.Time, extraHosts []string) (cert, key []byte, e error) {
23 now := time.Now()
24 if validUntil.Before(now) {
25 return nil, nil, errors.New("validUntil would create an already-expired certificate")
26 }
27 priv, e := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
28 if e != nil {
29 return nil, nil, e
30 }
31 // end of ASN.1 time
32 endOfTime := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
33 if validUntil.After(endOfTime) {
34 validUntil = endOfTime
35 }
36 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
37 serialNumber, e := rand.Int(rand.Reader, serialNumberLimit)
38 if e != nil {
39 return nil, nil, fmt.Errorf("failed to generate serial number: %s", e)
40 }
41 host, e := os.Hostname()
42 if e != nil {
43 return nil, nil, e
44 }
45 ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}
46 dnsNames := []string{host}
47 if host != "localhost" {
48 dnsNames = append(dnsNames, "localhost")
49 }
50 addIP := func(ipAddr net.IP) {
51 for _, ip := range ipAddresses {
52 if ip.Equal(ipAddr) {
53 return
54 }
55 }
56 ipAddresses = append(ipAddresses, ipAddr)
57 }
58 addHost := func(host string) {
59 for _, dnsName := range dnsNames {
60 if host == dnsName {
61 return
62 }
63 }
64 dnsNames = append(dnsNames, host)
65 }
66 addrs, e := interfaceAddrs()
67 if e != nil {
68 return nil, nil, e
69 }
70 for _, a := range addrs {
71 var ipAddr net.IP
72 ipAddr, _, e = net.ParseCIDR(a.String())
73 if e == nil {
74 addIP(ipAddr)
75 }
76 }
77 for _, hostStr := range extraHosts {
78 host, _, e = net.SplitHostPort(hostStr)
79 if e != nil {
80 host = hostStr
81 }
82 if ip := net.ParseIP(host); ip != nil {
83 addIP(ip)
84 } else {
85 addHost(host)
86 }
87 }
88 template := x509.Certificate{
89 SerialNumber: serialNumber,
90 Subject: pkix.Name{
91 Organization: []string{organization},
92 CommonName: host,
93 },
94 NotBefore: now.Add(-time.Hour * 24),
95 NotAfter: validUntil,
96 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature |
97 x509.KeyUsageCertSign,
98 IsCA: true, // so can sign self.
99 BasicConstraintsValid: true,
100 DNSNames: dnsNames,
101 IPAddresses: ipAddresses,
102 }
103 derBytes, e := x509.CreateCertificate(
104 rand.Reader, &template,
105 &template, &priv.PublicKey, priv,
106 )
107 if e != nil {
108 return nil, nil, fmt.Errorf("failed to create certificate: %v", e)
109 }
110 certBuf := &bytes.Buffer{}
111 e = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
112 if e != nil {
113 return nil, nil, fmt.Errorf("failed to encode certificate: %v", e)
114 }
115 keybytes, e := x509.MarshalECPrivateKey(priv)
116 if e != nil {
117 return nil, nil, fmt.Errorf("failed to marshal private key: %v", e)
118 }
119 keyBuf := &bytes.Buffer{}
120 e = pem.Encode(keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keybytes})
121 if e != nil {
122 return nil, nil, fmt.Errorf("failed to encode private key: %v", e)
123 }
124 return certBuf.Bytes(), keyBuf.Bytes(), nil
125 }
126