From b6517662319f558831b2f42384e6eb6cc9cd5fa5 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Fri, 10 Jan 2014 14:38:26 -0500 Subject: [PATCH] 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. --- certgen.go | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 certgen.go diff --git a/certgen.go b/certgen.go new file mode 100644 index 0000000..6ad14ba --- /dev/null +++ b/certgen.go @@ -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 +}