diff --git a/certgen.go b/certgen.go index a3eee94bb..26d16293a 100644 --- a/certgen.go +++ b/certgen.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2013-2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -48,33 +48,32 @@ func NewTLSCertPair(organization string, validUntil time.Time, extraHosts []stri return nil, nil, fmt.Errorf("failed to generate serial number: %s", err) } - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{organization}, - }, - NotBefore: now.Add(-time.Hour * 24), - NotAfter: validUntil, - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | - x509.KeyUsageCertSign, - 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"), + ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} + dnsNames := []string{host} + if host != "localhost" { + dnsNames = append(dnsNames, "localhost") + } + + addIP := func(ipAddr net.IP) { + for _, ip := range ipAddresses { + if bytes.Equal(ip, ipAddr) { + return + } + } + ipAddresses = append(ipAddresses, ipAddr) } - dnsNames := map[string]bool{ - host: true, - "localhost": true, + addHost := func(host string) { + for _, dnsName := range dnsNames { + if host == dnsName { + return + } + } + dnsNames = append(dnsNames, host) } addrs, err := interfaceAddrs() @@ -82,9 +81,9 @@ func NewTLSCertPair(organization string, validUntil time.Time, extraHosts []stri return nil, nil, err } for _, a := range addrs { - ip, _, err := net.ParseCIDR(a.String()) + ipAddr, _, err := net.ParseCIDR(a.String()) if err == nil { - ipAddresses[ip.String()] = ip + addIP(ipAddr) } } @@ -94,19 +93,28 @@ func NewTLSCertPair(organization string, validUntil time.Time, extraHosts []stri host = hostStr } if ip := net.ParseIP(host); ip != nil { - ipAddresses[ip.String()] = ip + addIP(ip) } else { - dnsNames[host] = true + addHost(host) } } - 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) + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{organization}, + CommonName: host, + }, + NotBefore: now.Add(-time.Hour * 24), + NotAfter: validUntil, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | + x509.KeyUsageCertSign, + IsCA: true, // so can sign self. + BasicConstraintsValid: true, + + DNSNames: dnsNames, + IPAddresses: ipAddresses, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, diff --git a/certgen_test.go b/certgen_test.go index 6771eefd7..f9e2c95ec 100644 --- a/certgen_test.go +++ b/certgen_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2013-2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -7,6 +7,7 @@ package btcutil_test import ( "crypto/x509" "encoding/pem" + "net" "testing" "time" @@ -21,7 +22,7 @@ func TestNewTLSCertPair(t *testing.T) { // differences. validUntil := time.Unix(time.Now().Add(10*365*24*time.Hour).Unix(), 0) org := "test autogenerated cert" - extraHosts := []string{"testtlscert.bogus", "127.0.0.1"} + extraHosts := []string{"testtlscert.bogus", "localhost", "127.0.0.1"} cert, key, err := btcutil.NewTLSCertPair(org, validUntil, extraHosts) if err != nil { t.Fatalf("failed with unexpected error: %v", err) @@ -76,6 +77,33 @@ func TestNewTLSCertPair(t *testing.T) { } } + // Ensure that the Common Name is also the first SAN DNS name. + cn := x509Cert.Subject.CommonName + san0 := x509Cert.DNSNames[0] + if cn != san0 { + t.Errorf("common name %s does not match first SAN %s", cn, san0) + } + + // Ensure there are no duplicate hosts or IPs. + hostCounts := make(map[string]int) + for _, host := range x509Cert.DNSNames { + hostCounts[host]++ + } + ipCounts := make(map[string]int) + for _, ip := range x509Cert.IPAddresses { + ipCounts[string(ip)]++ + } + for host, count := range hostCounts { + if count != 1 { + t.Errorf("host %s appears %d times in certificate", host, count) + } + } + for ipStr, count := range ipCounts { + if count != 1 { + t.Errorf("ip %s appears %d times in certificate", net.IP(ipStr), count) + } + } + // Ensure the cert can be use for the intended purposes. if !x509Cert.IsCA { t.Fatal("generated cert is not a certificate authority")