Add NewTLSCertPair to generate a certificate pair.

btcd, btcwallet, and an upcomming standalone tool to generate the TLS
certificates all need a way to generate TLS certificate pairs.  This
function, adapted from the cert gen code in btcd, abstracts this logic
so all programs can reuse the code.

Unlike the older btcd certificate generation code, this new function
allows specifying additional hostnames and IPs to add to the list of
addresses for which the cert is valid.
This commit is contained in:
Josh Rickmar 2014-01-10 14:38:26 -05:00
parent 2af3c8263a
commit b651766231

123
certgen.go Normal file
View file

@ -0,0 +1,123 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcutil
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
_ "crypto/sha512" // Needed for RegisterHash in init
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net"
"os"
"time"
)
// NewTLSCertPair returns a new PEM-encoded x.509 certificate pair
// based on a 521-bit ECDSA private key. The machine's local interface
// addresses and all variants of IPv4 and IPv6 localhost are included as
// valid IP addresses.
func NewTLSCertPair(organization string, validUntil time.Time, extraHosts []string) (cert, key []byte, err error) {
now := time.Now()
if validUntil.Before(now) {
return nil, nil, errors.New("validUntil would create an already-expired certificate")
}
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, err
}
// end of ASN.1 time
endOfTime := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
if validUntil.After(endOfTime) {
validUntil = endOfTime
}
template := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(0),
Subject: pkix.Name{
Organization: []string{organization},
},
NotBefore: now,
NotAfter: validUntil,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IsCA: true, // so can sign self.
BasicConstraintsValid: true,
}
host, err := os.Hostname()
if err != nil {
return nil, nil, err
}
// Use maps to prevent adding duplicates.
ipAddresses := map[string]net.IP{
"127.0.0.1": net.ParseIP("127.0.0.1"),
"::1": net.ParseIP("::1"),
}
dnsNames := map[string]bool{
host: true,
"localhost": true,
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, nil, err
}
for _, a := range addrs {
ip, _, err := net.ParseCIDR(a.String())
if err == nil {
ipAddresses[ip.String()] = ip
}
}
for _, hostStr := range extraHosts {
host, _, err := net.SplitHostPort(hostStr)
if err != nil {
host = hostStr
}
if ip := net.ParseIP(host); ip != nil {
ipAddresses[ip.String()] = ip
} else {
dnsNames[host] = true
}
}
template.DNSNames = make([]string, 0, len(dnsNames))
for dnsName := range dnsNames {
template.DNSNames = append(template.DNSNames, dnsName)
}
template.IPAddresses = make([]net.IP, 0, len(ipAddresses))
for _, ip := range ipAddresses {
template.IPAddresses = append(template.IPAddresses, ip)
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template,
&template, &priv.PublicKey, priv)
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate: %v\n", err)
}
certBuf := &bytes.Buffer{}
pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
keybytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, nil, err
}
keyBuf := &bytes.Buffer{}
pem.Encode(keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keybytes})
return certBuf.Bytes(), keyBuf.Bytes(), nil
}