From 9ffb1ecd80cfeccecdb4f0c82855c5abdd60e1a2 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Thu, 11 Jun 2015 16:08:04 -0400 Subject: [PATCH] Add Common Name to certificate. Some applications fail to parse the certificate if the CN is not set, even if they (correctly) check SANs before the CN when validating a hostname. Even though the CN should be ignored if a matching SAN hostname was found, we can prevent the parse from failing by also including the hostname as the CN. Additionally, switch from maps to slices to prevent DNS names and IP addresses from being reordered when added to the certificate template. --- certgen.go | 74 +++++++++++++++++++++++++++---------------------- certgen_test.go | 32 +++++++++++++++++++-- 2 files changed, 71 insertions(+), 35 deletions(-) diff --git a/certgen.go b/certgen.go index a3eee94..26d1629 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") } - dnsNames := map[string]bool{ - host: true, - "localhost": true, + + addIP := func(ipAddr net.IP) { + for _, ip := range ipAddresses { + if bytes.Equal(ip, ipAddr) { + return + } + } + ipAddresses = append(ipAddresses, ipAddr) + } + 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 6771eef..f9e2c95 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")