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