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