1// Copyright (c) 2013-2015 The btcsuite developers
2// Use of this source code is governed by an ISC
3// license that can be found in the LICENSE file.
4
5package btcutil_test
6
7import (
8	"crypto/x509"
9	"encoding/pem"
10	"net"
11	"testing"
12	"time"
13
14	"github.com/btcsuite/btcutil"
15	//"github.com/davecgh/go-spew/spew"
16)
17
18// TestNewTLSCertPair ensures the NewTLSCertPair function works as expected.
19func TestNewTLSCertPair(t *testing.T) {
20	// Certs don't support sub-second precision, so truncate it now to
21	// ensure the checks later don't fail due to nanosecond precision
22	// differences.
23	validUntil := time.Unix(time.Now().Add(10*365*24*time.Hour).Unix(), 0)
24	org := "test autogenerated cert"
25	extraHosts := []string{"testtlscert.bogus", "localhost", "127.0.0.1"}
26	cert, key, err := btcutil.NewTLSCertPair(org, validUntil, extraHosts)
27	if err != nil {
28		t.Fatalf("failed with unexpected error: %v", err)
29	}
30
31	// Ensure the PEM-encoded cert that is returned can be decoded.
32	pemCert, _ := pem.Decode(cert)
33	if pemCert == nil {
34		t.Fatalf("pem.Decode was unable to decode the certificate")
35	}
36
37	// Ensure the PEM-encoded key that is returned can be decoded.
38	pemKey, _ := pem.Decode(key)
39	if pemCert == nil {
40		t.Fatalf("pem.Decode was unable to decode the key")
41	}
42
43	// Ensure the DER-encoded key bytes can be successfully parsed.
44	_, err = x509.ParseECPrivateKey(pemKey.Bytes)
45	if err != nil {
46		t.Fatalf("failed with unexpected error: %v", err)
47	}
48
49	// Ensure the DER-encoded cert bytes can be successfully into an X.509
50	// certificate.
51	x509Cert, err := x509.ParseCertificate(pemCert.Bytes)
52	if err != nil {
53		t.Fatalf("failed with unexpected error: %v", err)
54	}
55
56	// Ensure the specified organization is correct.
57	x509Orgs := x509Cert.Subject.Organization
58	if len(x509Orgs) == 0 || x509Orgs[0] != org {
59		x509Org := "<no organization>"
60		if len(x509Orgs) > 0 {
61			x509Org = x509Orgs[0]
62		}
63		t.Fatalf("generated cert organization field mismatch, got "+
64			"'%v', want '%v'", x509Org, org)
65	}
66
67	// Ensure the specified valid until value is correct.
68	if !x509Cert.NotAfter.Equal(validUntil) {
69		t.Fatalf("generated cert valid until field mismatch, got %v, "+
70			"want %v", x509Cert.NotAfter, validUntil)
71	}
72
73	// Ensure the specified extra hosts are present.
74	for _, host := range extraHosts {
75		if err := x509Cert.VerifyHostname(host); err != nil {
76			t.Fatalf("failed to verify extra host '%s'", host)
77		}
78	}
79
80	// Ensure that the Common Name is also the first SAN DNS name.
81	cn := x509Cert.Subject.CommonName
82	san0 := x509Cert.DNSNames[0]
83	if cn != san0 {
84		t.Errorf("common name %s does not match first SAN %s", cn, san0)
85	}
86
87	// Ensure there are no duplicate hosts or IPs.
88	hostCounts := make(map[string]int)
89	for _, host := range x509Cert.DNSNames {
90		hostCounts[host]++
91	}
92	ipCounts := make(map[string]int)
93	for _, ip := range x509Cert.IPAddresses {
94		ipCounts[string(ip)]++
95	}
96	for host, count := range hostCounts {
97		if count != 1 {
98			t.Errorf("host %s appears %d times in certificate", host, count)
99		}
100	}
101	for ipStr, count := range ipCounts {
102		if count != 1 {
103			t.Errorf("ip %s appears %d times in certificate", net.IP(ipStr), count)
104		}
105	}
106
107	// Ensure the cert can be use for the intended purposes.
108	if !x509Cert.IsCA {
109		t.Fatal("generated cert is not a certificate authority")
110	}
111	if x509Cert.KeyUsage&x509.KeyUsageKeyEncipherment == 0 {
112		t.Fatal("generated cert can't be used for key encipherment")
113	}
114	if x509Cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
115		t.Fatal("generated cert can't be used for digital signatures")
116	}
117	if x509Cert.KeyUsage&x509.KeyUsageCertSign == 0 {
118		t.Fatal("generated cert can't be used for signing other certs")
119	}
120	if !x509Cert.BasicConstraintsValid {
121		t.Fatal("generated cert does not have valid basic constraints")
122	}
123}
124