Skip to content

Commit

Permalink
Add Common Name to certificate.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jrick committed Jun 15, 2015
1 parent 53b0b8c commit 9ffb1ec
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 35 deletions.
74 changes: 41 additions & 33 deletions certgen.go
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -48,43 +48,42 @@ 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()
if err != nil {
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)
}
}

Expand All @@ -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,
Expand Down
32 changes: 30 additions & 2 deletions certgen_test.go
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -7,6 +7,7 @@ package btcutil_test
import (
"crypto/x509"
"encoding/pem"
"net"
"testing"
"time"

Expand All @@ -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)
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 9ffb1ec

Please sign in to comment.