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.
This commit is contained in:
parent
53b0b8cd09
commit
9ffb1ecd80
2 changed files with 71 additions and 35 deletions
74
certgen.go
74
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,
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue