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