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