github.com/letsencrypt/boulder@v0.20251208.0/identifier/identifier_test.go (about) 1 package identifier 2 3 import ( 4 "crypto/x509" 5 "crypto/x509/pkix" 6 "net" 7 "net/netip" 8 "reflect" 9 "slices" 10 "testing" 11 ) 12 13 func TestNewIP(t *testing.T) { 14 cases := []struct { 15 name string 16 ip netip.Addr 17 want ACMEIdentifier 18 }{ 19 { 20 name: "IPv4 address", 21 ip: netip.MustParseAddr("9.9.9.9"), 22 want: ACMEIdentifier{Type: TypeIP, Value: "9.9.9.9"}, 23 }, 24 { 25 name: "IPv6 address", 26 ip: netip.MustParseAddr("fe80::cafe"), 27 want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"}, 28 }, 29 { 30 name: "IPv6 address with scope zone", 31 ip: netip.MustParseAddr("fe80::cafe%lo"), 32 want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"}, 33 }, 34 } 35 for _, tc := range cases { 36 t.Run(tc.name, func(t *testing.T) { 37 t.Parallel() 38 got := NewIP(tc.ip) 39 if got != tc.want { 40 t.Errorf("NewIP(%#v) = %#v, but want %#v", tc.ip, got, tc.want) 41 } 42 }) 43 } 44 } 45 46 // TestFromX509 tests FromCert and FromCSR, which are fromX509's public 47 // wrappers. 48 func TestFromX509(t *testing.T) { 49 cases := []struct { 50 name string 51 subject pkix.Name 52 dnsNames []string 53 ipAddresses []net.IP 54 want ACMEIdentifiers 55 }{ 56 { 57 name: "no explicit CN", 58 dnsNames: []string{"a.com"}, 59 want: ACMEIdentifiers{NewDNS("a.com")}, 60 }, 61 { 62 name: "explicit uppercase CN", 63 subject: pkix.Name{CommonName: "A.com"}, 64 dnsNames: []string{"a.com"}, 65 want: ACMEIdentifiers{NewDNS("a.com")}, 66 }, 67 { 68 name: "no explicit CN, uppercase SAN", 69 dnsNames: []string{"A.com"}, 70 want: ACMEIdentifiers{NewDNS("a.com")}, 71 }, 72 { 73 name: "duplicate SANs", 74 dnsNames: []string{"b.com", "b.com", "a.com", "a.com"}, 75 want: ACMEIdentifiers{NewDNS("a.com"), NewDNS("b.com")}, 76 }, 77 { 78 name: "explicit CN not found in SANs", 79 subject: pkix.Name{CommonName: "a.com"}, 80 dnsNames: []string{"b.com"}, 81 want: ACMEIdentifiers{NewDNS("a.com"), NewDNS("b.com")}, 82 }, 83 { 84 name: "mix of DNSNames and IPAddresses", 85 dnsNames: []string{"a.com"}, 86 ipAddresses: []net.IP{{192, 168, 1, 1}}, 87 want: ACMEIdentifiers{NewDNS("a.com"), NewIP(netip.MustParseAddr("192.168.1.1"))}, 88 }, 89 } 90 for _, tc := range cases { 91 t.Run("cert/"+tc.name, func(t *testing.T) { 92 t.Parallel() 93 got := FromCert(&x509.Certificate{Subject: tc.subject, DNSNames: tc.dnsNames, IPAddresses: tc.ipAddresses}) 94 if !slices.Equal(got, tc.want) { 95 t.Errorf("FromCert() got %#v, but want %#v", got, tc.want) 96 } 97 }) 98 t.Run("csr/"+tc.name, func(t *testing.T) { 99 t.Parallel() 100 got := FromCSR(&x509.CertificateRequest{Subject: tc.subject, DNSNames: tc.dnsNames, IPAddresses: tc.ipAddresses}) 101 if !slices.Equal(got, tc.want) { 102 t.Errorf("FromCSR() got %#v, but want %#v", got, tc.want) 103 } 104 }) 105 } 106 } 107 108 func TestNormalize(t *testing.T) { 109 cases := []struct { 110 name string 111 idents ACMEIdentifiers 112 want ACMEIdentifiers 113 }{ 114 { 115 name: "convert to lowercase", 116 idents: ACMEIdentifiers{ 117 {Type: TypeDNS, Value: "AlPha.example.coM"}, 118 {Type: TypeIP, Value: "fe80::CAFE"}, 119 }, 120 want: ACMEIdentifiers{ 121 {Type: TypeDNS, Value: "alpha.example.com"}, 122 {Type: TypeIP, Value: "fe80::cafe"}, 123 }, 124 }, 125 { 126 name: "sort", 127 idents: ACMEIdentifiers{ 128 {Type: TypeDNS, Value: "foobar.com"}, 129 {Type: TypeDNS, Value: "bar.com"}, 130 {Type: TypeDNS, Value: "baz.com"}, 131 {Type: TypeDNS, Value: "a.com"}, 132 {Type: TypeIP, Value: "fe80::cafe"}, 133 {Type: TypeIP, Value: "2001:db8::1dea"}, 134 {Type: TypeIP, Value: "192.168.1.1"}, 135 }, 136 want: ACMEIdentifiers{ 137 {Type: TypeDNS, Value: "a.com"}, 138 {Type: TypeDNS, Value: "bar.com"}, 139 {Type: TypeDNS, Value: "baz.com"}, 140 {Type: TypeDNS, Value: "foobar.com"}, 141 {Type: TypeIP, Value: "192.168.1.1"}, 142 {Type: TypeIP, Value: "2001:db8::1dea"}, 143 {Type: TypeIP, Value: "fe80::cafe"}, 144 }, 145 }, 146 { 147 name: "de-duplicate", 148 idents: ACMEIdentifiers{ 149 {Type: TypeDNS, Value: "AlPha.example.coM"}, 150 {Type: TypeIP, Value: "fe80::CAFE"}, 151 {Type: TypeDNS, Value: "alpha.example.com"}, 152 {Type: TypeIP, Value: "fe80::cafe"}, 153 NewIP(netip.MustParseAddr("fe80:0000:0000:0000:0000:0000:0000:cafe")), 154 }, 155 want: ACMEIdentifiers{ 156 {Type: TypeDNS, Value: "alpha.example.com"}, 157 {Type: TypeIP, Value: "fe80::cafe"}, 158 }, 159 }, 160 { 161 name: "DNS before IP", 162 idents: ACMEIdentifiers{ 163 {Type: TypeIP, Value: "fe80::cafe"}, 164 {Type: TypeDNS, Value: "alpha.example.com"}, 165 }, 166 want: ACMEIdentifiers{ 167 {Type: TypeDNS, Value: "alpha.example.com"}, 168 {Type: TypeIP, Value: "fe80::cafe"}, 169 }, 170 }, 171 } 172 for _, tc := range cases { 173 t.Run(tc.name, func(t *testing.T) { 174 t.Parallel() 175 got := Normalize(tc.idents) 176 if !slices.Equal(got, tc.want) { 177 t.Errorf("Got %#v, but want %#v", got, tc.want) 178 } 179 }) 180 } 181 } 182 183 func TestToValues(t *testing.T) { 184 cases := []struct { 185 name string 186 idents ACMEIdentifiers 187 wantErr string 188 wantDnsNames []string 189 wantIpAddresses []net.IP 190 }{ 191 { 192 name: "DNS names and IP addresses", 193 // These are deliberately out of alphabetical and type order, to 194 // ensure ToValues doesn't do normalization, which ought to be done 195 // explicitly. 196 idents: ACMEIdentifiers{ 197 {Type: TypeDNS, Value: "beta.example.com"}, 198 {Type: TypeIP, Value: "fe80::cafe"}, 199 {Type: TypeDNS, Value: "alpha.example.com"}, 200 {Type: TypeIP, Value: "127.0.0.1"}, 201 }, 202 wantErr: "", 203 wantDnsNames: []string{"beta.example.com", "alpha.example.com"}, 204 wantIpAddresses: []net.IP{net.ParseIP("fe80::cafe"), net.ParseIP("127.0.0.1")}, 205 }, 206 { 207 name: "DNS names only", 208 idents: ACMEIdentifiers{ 209 {Type: TypeDNS, Value: "alpha.example.com"}, 210 {Type: TypeDNS, Value: "beta.example.com"}, 211 }, 212 wantErr: "", 213 wantDnsNames: []string{"alpha.example.com", "beta.example.com"}, 214 wantIpAddresses: nil, 215 }, 216 { 217 name: "IP addresses only", 218 idents: ACMEIdentifiers{ 219 {Type: TypeIP, Value: "127.0.0.1"}, 220 {Type: TypeIP, Value: "fe80::cafe"}, 221 }, 222 wantErr: "", 223 wantDnsNames: nil, 224 wantIpAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("fe80::cafe")}, 225 }, 226 { 227 name: "invalid IP address", 228 idents: ACMEIdentifiers{ 229 {Type: TypeIP, Value: "fe80::c0ffee"}, 230 }, 231 wantErr: "parsing IP address: fe80::c0ffee", 232 wantDnsNames: nil, 233 wantIpAddresses: nil, 234 }, 235 { 236 name: "invalid identifier type", 237 idents: ACMEIdentifiers{ 238 {Type: "fnord", Value: "panic.example.com"}, 239 }, 240 wantErr: "evaluating identifier type: fnord for panic.example.com", 241 wantDnsNames: nil, 242 wantIpAddresses: nil, 243 }, 244 } 245 for _, tc := range cases { 246 t.Run(tc.name, func(t *testing.T) { 247 t.Parallel() 248 gotDnsNames, gotIpAddresses, gotErr := tc.idents.ToValues() 249 if !slices.Equal(gotDnsNames, tc.wantDnsNames) { 250 t.Errorf("Got DNS names %#v, but want %#v", gotDnsNames, tc.wantDnsNames) 251 } 252 if !reflect.DeepEqual(gotIpAddresses, tc.wantIpAddresses) { 253 t.Errorf("Got IP addresses %#v, but want %#v", gotIpAddresses, tc.wantIpAddresses) 254 } 255 if tc.wantErr != "" && (gotErr.Error() != tc.wantErr) { 256 t.Errorf("Got error %#v, but want %#v", gotErr.Error(), tc.wantErr) 257 } 258 if tc.wantErr == "" && gotErr != nil { 259 t.Errorf("Got error %#v, but didn't want one", gotErr.Error()) 260 } 261 }) 262 } 263 }