lbcutil/certgen.go
Josh Rickmar b651766231 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.
2014-01-10 15:35:55 -05:00

123 lines
3.1 KiB
Go

// 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
}