View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.ssl;
29  
30  import java.io.ByteArrayInputStream;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.io.InputStreamReader;
34  import java.nio.charset.StandardCharsets;
35  import java.security.cert.CertificateFactory;
36  import java.security.cert.X509Certificate;
37  import java.util.Arrays;
38  import java.util.Collections;
39  import java.util.List;
40  
41  import javax.net.ssl.SSLException;
42  
43  import org.apache.hc.client5.http.psl.DomainType;
44  import org.apache.hc.client5.http.psl.PublicSuffixList;
45  import org.apache.hc.client5.http.psl.PublicSuffixListParser;
46  import org.apache.hc.client5.http.psl.PublicSuffixMatcher;
47  import org.junit.jupiter.api.Assertions;
48  import org.junit.jupiter.api.BeforeEach;
49  import org.junit.jupiter.api.Test;
50  
51  /**
52   * Unit tests for {@link org.apache.hc.client5.http.ssl.DefaultHostnameVerifier}.
53   */
54  class TestDefaultHostnameVerifier {
55  
56      private DefaultHostnameVerifier impl;
57      private PublicSuffixMatcher publicSuffixMatcher;
58  
59      private static final String PUBLIC_SUFFIX_MATCHER_SOURCE_FILE = "suffixlistmatcher.txt";
60  
61      @BeforeEach
62      void setup() throws IOException {
63          impl = new DefaultHostnameVerifier();
64  
65          // Load the test PublicSuffixMatcher
66          final ClassLoader classLoader = getClass().getClassLoader();
67          final InputStream in = classLoader.getResourceAsStream(PUBLIC_SUFFIX_MATCHER_SOURCE_FILE);
68          Assertions.assertNotNull(in);
69          final List<PublicSuffixList> lists = PublicSuffixListParser.INSTANCE.parseByType(
70                  new InputStreamReader(in, StandardCharsets.UTF_8));
71          publicSuffixMatcher = new PublicSuffixMatcher(lists);
72      }
73  
74      @Test
75      void testVerify() throws Exception {
76          final CertificateFactory cf = CertificateFactory.getInstance("X.509");
77          InputStream in;
78          X509Certificate x509;
79          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO);
80          x509 = (X509Certificate) cf.generateCertificate(in);
81  
82          impl.verify("foo.com", x509);
83          exceptionPlease(impl, "a.foo.com", x509);
84          exceptionPlease(impl, "bar.com", x509);
85  
86          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_HANAKO);
87          x509 = (X509Certificate) cf.generateCertificate(in);
88          impl.verify("\u82b1\u5b50.co.jp", x509);
89          exceptionPlease(impl, "a.\u82b1\u5b50.co.jp", x509);
90  
91          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR);
92          x509 = (X509Certificate) cf.generateCertificate(in);
93          exceptionPlease(impl, "foo.com", x509);
94          exceptionPlease(impl, "a.foo.com", x509);
95          impl.verify("bar.com", x509);
96          exceptionPlease(impl, "a.bar.com", x509);
97  
98          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR_HANAKO);
99          x509 = (X509Certificate) cf.generateCertificate(in);
100         exceptionPlease(impl, "foo.com", x509);
101         exceptionPlease(impl, "a.foo.com", x509);
102         impl.verify("bar.com", x509);
103         exceptionPlease(impl, "a.bar.com", x509);
104 
105         /*
106            Java isn't extracting international subjectAlts properly.  (Or
107            OpenSSL isn't storing them properly).
108         */
109         // DEFAULT.verify("\u82b1\u5b50.co.jp", x509 );
110         // impl.verify("\u82b1\u5b50.co.jp", x509 );
111         exceptionPlease(impl, "a.\u82b1\u5b50.co.jp", x509);
112 
113         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_NO_CNS_FOO);
114         x509 = (X509Certificate) cf.generateCertificate(in);
115         impl.verify("foo.com", x509);
116         exceptionPlease(impl, "a.foo.com", x509);
117 
118         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_NO_CNS_FOO);
119         x509 = (X509Certificate) cf.generateCertificate(in);
120         impl.verify("foo.com", x509);
121         exceptionPlease(impl, "a.foo.com", x509);
122 
123         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_THREE_CNS_FOO_BAR_HANAKO);
124         x509 = (X509Certificate) cf.generateCertificate(in);
125         exceptionPlease(impl, "foo.com", x509);
126         exceptionPlease(impl, "a.foo.com", x509);
127         exceptionPlease(impl, "bar.com", x509);
128         exceptionPlease(impl, "a.bar.com", x509);
129         impl.verify("\u82b1\u5b50.co.jp", x509);
130         exceptionPlease(impl, "a.\u82b1\u5b50.co.jp", x509);
131 
132         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_FOO);
133         x509 = (X509Certificate) cf.generateCertificate(in);
134         exceptionPlease(impl, "foo.com", x509);
135         impl.verify("www.foo.com", x509);
136         impl.verify("\u82b1\u5b50.foo.com", x509);
137         exceptionPlease(impl, "a.b.foo.com", x509);
138 
139         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_CO_JP);
140         x509 = (X509Certificate) cf.generateCertificate(in);
141         // Silly test because no-one would ever be able to lookup an IP address
142         // using "*.co.jp".
143         impl.verify("*.co.jp", x509);
144         impl.verify("foo.co.jp", x509);
145         impl.verify("\u82b1\u5b50.co.jp", x509);
146 
147         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_FOO_BAR_HANAKO);
148         x509 = (X509Certificate) cf.generateCertificate(in);
149         // try the foo.com variations
150         exceptionPlease(impl, "foo.com", x509);
151         exceptionPlease(impl, "www.foo.com", x509);
152         exceptionPlease(impl, "\u82b1\u5b50.foo.com", x509);
153         exceptionPlease(impl, "a.b.foo.com", x509);
154         // try the bar.com variations
155         exceptionPlease(impl, "bar.com", x509);
156         impl.verify("www.bar.com", x509);
157         impl.verify("\u82b1\u5b50.bar.com", x509);
158         exceptionPlease(impl, "a.b.bar.com", x509);
159 
160         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_VALUE_AVA);
161         x509 = (X509Certificate) cf.generateCertificate(in);
162         impl.verify("repository.infonotary.com", x509);
163 
164         in = new ByteArrayInputStream(CertificatesToPlayWith.S_GOOGLE_COM);
165         x509 = (X509Certificate) cf.generateCertificate(in);
166         impl.verify("*.google.com", x509);
167 
168         in = new ByteArrayInputStream(CertificatesToPlayWith.S_GOOGLE_COM);
169         x509 = (X509Certificate) cf.generateCertificate(in);
170         impl.verify("*.Google.com", x509);
171 
172         in = new ByteArrayInputStream(CertificatesToPlayWith.IP_1_1_1_1);
173         x509 = (X509Certificate) cf.generateCertificate(in);
174         impl.verify("1.1.1.1", x509);
175         impl.verify("dummy-value.com", x509);
176 
177         exceptionPlease(impl, "1.1.1.2", x509);
178         exceptionPlease(impl, "not-the-cn.com", x509);
179 
180         in = new ByteArrayInputStream(CertificatesToPlayWith.EMAIL_ALT_SUBJECT_NAME);
181         x509 = (X509Certificate) cf.generateCertificate(in);
182         impl.verify("www.company.com", x509);
183     }
184 
185     @Test
186     void testSubjectAlt() throws Exception {
187         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
188         final InputStream in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_SUBJECT_ALT);
189         final X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);
190 
191         Assertions.assertEquals("CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CH",
192                 x509.getSubjectDN().getName());
193 
194         impl.verify("localhost.localdomain", x509);
195         impl.verify("127.0.0.1", x509);
196 
197         Assertions.assertThrows(SSLException.class, () -> impl.verify("localhost", x509));
198         Assertions.assertThrows(SSLException.class, () -> impl.verify("local.host", x509));
199         Assertions.assertThrows(SSLException.class, () -> impl.verify("127.0.0.2", x509));
200     }
201 
202     public void exceptionPlease(final DefaultHostnameVerifier hv, final String host,
203                                 final X509Certificate x509) {
204         Assertions.assertThrows(SSLException.class, () -> hv.verify(host, x509));
205     }
206 
207     @Test
208     void testParseFQDN() {
209         Assertions.assertEquals(Arrays.asList("blah"),
210                 DefaultHostnameVerifier.parseFQDN("blah"));
211         Assertions.assertEquals(Arrays.asList("blah", "blah"),
212                 DefaultHostnameVerifier.parseFQDN("blah.blah"));
213         Assertions.assertEquals(Arrays.asList("blah", "blah", "blah"),
214                 DefaultHostnameVerifier.parseFQDN("blah.blah.blah"));
215         Assertions.assertEquals(Arrays.asList("", "", "blah", ""),
216                 DefaultHostnameVerifier.parseFQDN(".blah.."));
217         Assertions.assertEquals(Arrays.asList(""),
218                 DefaultHostnameVerifier.parseFQDN(""));
219         Assertions.assertEquals(Arrays.asList("", ""),
220                 DefaultHostnameVerifier.parseFQDN("."));
221         Assertions.assertEquals(Arrays.asList("com", "domain", "host"),
222                 DefaultHostnameVerifier.parseFQDN("host.domain.com"));
223     }
224 
225     @Test
226     void testDomainRootMatching() {
227         Assertions.assertFalse(DefaultHostnameVerifier.matchDomainRoot("a.b.c", null));
228         Assertions.assertTrue(DefaultHostnameVerifier.matchDomainRoot("a.b.c", "a.b.c"));
229         Assertions.assertFalse(DefaultHostnameVerifier.matchDomainRoot("aa.b.c", "a.b.c"));
230         Assertions.assertFalse(DefaultHostnameVerifier.matchDomainRoot("a.b.c", "aa.b.c"));
231         Assertions.assertTrue(DefaultHostnameVerifier.matchDomainRoot("a.a.b.c", "a.b.c"));
232     }
233 
234     @Test
235     void testIdentityMatching() {
236 
237         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.b.c", "*.b.c"));
238         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.b.c", "*.b.c"));
239 
240         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("s.a.b.c", "*.b.c"));
241         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("s.a.b.c", "*.b.c")); // subdomain not OK
242 
243         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.gov.uk", "*.gov.uk", publicSuffixMatcher));
244         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.gov.uk", "*.gov.uk", publicSuffixMatcher));  // Bad 2TLD
245 
246         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("s.a.gov.uk", "*.a.gov.uk", publicSuffixMatcher));
247         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("s.a.gov.uk", "*.a.gov.uk", publicSuffixMatcher));
248 
249         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("s.a.gov.uk", "*.gov.uk", publicSuffixMatcher));
250         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("s.a.gov.uk", "*.gov.uk", publicSuffixMatcher));  // BBad 2TLD/no subdomain allowed
251 
252         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.gov.com", "*.gov.com", publicSuffixMatcher));
253         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.gov.com", "*.gov.com", publicSuffixMatcher));
254 
255         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("s.a.gov.com", "*.gov.com", publicSuffixMatcher));
256         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("s.a.gov.com", "*.gov.com", publicSuffixMatcher)); // no subdomain allowed
257 
258         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.gov.uk", "a*.gov.uk", publicSuffixMatcher));
259         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.gov.uk", "a*.gov.uk", publicSuffixMatcher)); // Bad 2TLD
260 
261         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity("s.a.gov.uk", "a*.gov.uk", publicSuffixMatcher)); // Bad 2TLD
262         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("s.a.gov.uk", "a*.gov.uk", publicSuffixMatcher)); // Bad 2TLD/no subdomain allowed
263 
264         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity("a.b.c", "*.b.*"));
265         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("a.b.c", "*.b.*"));
266 
267         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity("a.b.c", "*.*.c"));
268         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("a.b.c", "*.*.c"));
269 
270         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.b.xxx.uk", "a.b.xxx.uk", publicSuffixMatcher));
271         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.b.xxx.uk", "a.b.xxx.uk", publicSuffixMatcher));
272 
273         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.b.xxx.uk", "*.b.xxx.uk", publicSuffixMatcher));
274         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.b.xxx.uk", "*.b.xxx.uk", publicSuffixMatcher));
275 
276         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity("b.xxx.uk", "b.xxx.uk", publicSuffixMatcher));
277         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("b.xxx.uk", "b.xxx.uk", publicSuffixMatcher));
278 
279         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity("b.xxx.uk", "*.xxx.uk", publicSuffixMatcher));
280         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("b.xxx.uk", "*.xxx.uk", publicSuffixMatcher));
281     }
282 
283     @Test
284     void testHTTPCLIENT_1097() {
285         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.b.c", "a*.b.c"));
286         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("a.b.c", "a*.b.c"));
287 
288         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("a.a.b.c", "a*.b.c"));
289         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict("a.a.b.c", "a*.b.c"));
290     }
291 
292     @Test
293     void testHTTPCLIENT_1255() {
294         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity("mail.a.b.c.com", "m*.a.b.c.com"));
295         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("mail.a.b.c.com", "m*.a.b.c.com"));
296     }
297 
298     @Test
299     void testHTTPCLIENT_1997_ANY() { // Only True on all domains
300         String domain;
301         // Unknown
302         domain = "dev.b.cloud.a";
303         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain));
304         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain));
305         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
306         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
307 
308         // ICANN
309         domain = "dev.b.cloud.com";
310         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain));
311         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain));
312         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
313         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
314 
315         // PRIVATE
316         domain = "dev.b.cloud.lan";
317         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain));
318         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain));
319         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
320         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
321     }
322 
323     @Test
324     void testHTTPCLIENT_1997_ICANN() { // Only True on ICANN domains
325         String domain;
326         // Unknown
327         domain = "dev.b.cloud.a";
328         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
329         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
330 
331         // ICANN
332         domain = "dev.b.cloud.com";
333         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
334         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
335 
336         // PRIVATE
337         domain = "dev.b.cloud.lan";
338         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
339         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
340     }
341 
342     @Test
343     void testHTTPCLIENT_1997_PRIVATE() { // Only True on PRIVATE domains
344         String domain;
345         // Unknown
346         domain = "dev.b.cloud.a";
347         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
348         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
349 
350         // ICANN
351         domain = "dev.b.cloud.com";
352         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
353         Assertions.assertFalse(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
354 
355         // PRIVATE
356         domain = "dev.b.cloud.lan";
357         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
358         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
359     }
360 
361     @Test
362     void testHTTPCLIENT_1997_UNKNOWN() { // Only True on all domains (same as ANY)
363         String domain;
364         // Unknown
365         domain = "dev.b.cloud.a";
366         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
367         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
368 
369         // ICANN
370         domain = "dev.b.cloud.com";
371         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
372         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
373 
374         // PRIVATE
375         domain = "dev.b.cloud.lan";
376         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentity(        "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
377         Assertions.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(  "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
378     }
379 
380     @Test // Check compressed IPv6 hostname matching
381     void testHTTPCLIENT_1316() throws Exception{
382         final String host1 = "2001:0db8:aaaa:bbbb:cccc:0:0:0001";
383         DefaultHostnameVerifier.matchIPv6Address(host1, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc:0:0:0001")));
384         DefaultHostnameVerifier.matchIPv6Address(host1, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::1")));
385         Assertions.assertThrows(SSLException.class, () ->
386                 DefaultHostnameVerifier.matchIPv6Address(host1, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::10"))));
387         final String host2 = "2001:0db8:aaaa:bbbb:cccc::1";
388         DefaultHostnameVerifier.matchIPv6Address(host2, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc:0:0:0001")));
389         DefaultHostnameVerifier.matchIPv6Address(host2, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::1")));
390         Assertions.assertThrows(SSLException.class, () ->
391                 DefaultHostnameVerifier.matchIPv6Address(host2, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::10"))));
392     }
393 
394     @Test
395     void testHTTPCLIENT_2149() throws Exception {
396         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
397         final InputStream in = new ByteArrayInputStream(CertificatesToPlayWith.SUBJECT_ALT_IP_ONLY);
398         final X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);
399 
400         Assertions.assertEquals("CN=www.foo.com", x509.getSubjectDN().getName());
401 
402         impl.verify("127.0.0.1", x509);
403         impl.verify("www.foo.com", x509);
404 
405         exceptionPlease(impl, "127.0.0.2", x509);
406         exceptionPlease(impl, "www.bar.com", x509);
407     }
408 
409     @Test
410     void testExtractCN() throws Exception {
411         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("cn=blah, ou=blah, o=blah"));
412         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("cn=blah, cn=yada, cn=booh"));
413         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("c = pampa ,  cn  =    blah    , ou = blah , o = blah"));
414         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("cn=\"blah\", ou=blah, o=blah"));
415         Assertions.assertEquals("blah  blah", DefaultHostnameVerifier.extractCN("cn=\"blah  blah\", ou=blah, o=blah"));
416         Assertions.assertEquals("blah, blah", DefaultHostnameVerifier.extractCN("cn=\"blah, blah\", ou=blah, o=blah"));
417         Assertions.assertEquals("blah, blah", DefaultHostnameVerifier.extractCN("cn=blah\\, blah, ou=blah, o=blah"));
418         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("c = cn=uuh, cn=blah, ou=blah, o=blah"));
419         Assertions.assertThrows(SSLException.class, () ->
420                 DefaultHostnameVerifier.extractCN("blah,blah"));
421         Assertions.assertThrows(SSLException.class, () ->
422                 DefaultHostnameVerifier.extractCN("cn,o=blah"));
423     }
424 
425     @Test
426     void testMatchDNSName() throws Exception {
427         DefaultHostnameVerifier.matchDNSName(
428                 "host.domain.com",
429                 Collections.singletonList(SubjectName.DNS("*.domain.com")),
430                 publicSuffixMatcher);
431         DefaultHostnameVerifier.matchDNSName(
432                 "host.xx",
433                 Collections.singletonList(SubjectName.DNS("*.xx")),
434                 publicSuffixMatcher);
435         DefaultHostnameVerifier.matchDNSName(
436                 "host.appspot.com",
437                 Collections.singletonList(SubjectName.DNS("*.appspot.com")),
438                 publicSuffixMatcher);
439         DefaultHostnameVerifier.matchDNSName(
440                 "demo-s3-bucket.s3.eu-central-1.amazonaws.com",
441                 Collections.singletonList(SubjectName.DNS("*.s3.eu-central-1.amazonaws.com")),
442                 publicSuffixMatcher);
443         DefaultHostnameVerifier.matchDNSName(
444                 "hostname-workspace-1.local",
445                 Collections.singletonList(SubjectName.DNS("hostname-workspace-1.local")),
446                 publicSuffixMatcher);
447 
448         Assertions.assertThrows(SSLException.class, () ->
449                 DefaultHostnameVerifier.matchDNSName(
450                         "host.domain.com",
451                         Collections.singletonList(SubjectName.DNS("some.other.com")),
452                         publicSuffixMatcher));
453 
454         DefaultHostnameVerifier.matchDNSName(
455                 "host.ec2.compute-1.amazonaws.com",
456                 Collections.singletonList(SubjectName.DNS("host.ec2.compute-1.amazonaws.com")),
457                 publicSuffixMatcher);
458         DefaultHostnameVerifier.matchDNSName(
459                 "host.ec2.compute-1.amazonaws.com",
460                 Collections.singletonList(SubjectName.DNS("*.ec2.compute-1.amazonaws.com")),
461                 publicSuffixMatcher);
462         Assertions.assertThrows(SSLException.class, () ->
463             DefaultHostnameVerifier.matchDNSName(
464                     "ec2.compute-1.amazonaws.com",
465                     Collections.singletonList(SubjectName.DNS("ec2.compute-1.amazonaws.com")),
466                     publicSuffixMatcher));
467         Assertions.assertThrows(SSLException.class, () ->
468                 DefaultHostnameVerifier.matchDNSName(
469                         "ec2.compute-1.amazonaws.com",
470                         Collections.singletonList(SubjectName.DNS("*.compute-1.amazonaws.com")),
471                         publicSuffixMatcher));
472     }
473 
474     @Test
475     void testMatchIdentity() {
476         // Test 1: IDN matching punycode
477         final String unicodeHost1 = "поиск-слов.рф";
478         final String punycodeHost1 = "xn----dtbqigoecuc.xn--p1ai";
479 
480         // These should now match, thanks to IDN.toASCII():
481         Assertions.assertTrue(
482                 DefaultHostnameVerifier.matchIdentity(unicodeHost1, punycodeHost1),
483                 "Expected the Unicode host and its punycode to match"
484         );
485 
486         // ‘example.com’ vs. an unrelated punycode domain should fail:
487         Assertions.assertFalse(
488                 DefaultHostnameVerifier.matchIdentity("example.com", punycodeHost1),
489                 "Expected mismatch between example.com and xn----dtbqigoecuc.xn--p1ai"
490         );
491 
492         // Test 2: Unicode host and Unicode identity
493         final String unicodeHost2 = "пример.рф";
494         final String unicodeIdentity2 = "пример.рф";
495         Assertions.assertTrue(
496                 DefaultHostnameVerifier.matchIdentity(unicodeHost2, unicodeIdentity2),
497                 "Expected Unicode host and Unicode identity to match"
498         );
499 
500         // Test 3: Punycode host and Unicode identity
501         final String unicodeHost3 = "пример.рф";
502         final String punycodeIdentity3 = "xn--e1afmkfd.xn--p1ai";
503         Assertions.assertTrue(
504                 DefaultHostnameVerifier.matchIdentity(unicodeHost3, punycodeIdentity3),
505                 "Expected Unicode host and punycode identity to match"
506         );
507 
508         // Test 4: Wildcard matching in the left-most label
509         final String unicodeHost4 = "sub.пример.рф";
510         final String unicodeIdentity4 = "*.пример.рф";
511         Assertions.assertTrue(
512                 DefaultHostnameVerifier.matchIdentity(unicodeHost4, unicodeIdentity4),
513                 "Expected wildcard to match subdomain"
514         );
515 
516         // Test 5: Invalid host
517         final String invalidHost = "invalid_host";
518         final String unicodeIdentity5 = "пример.рф";
519         Assertions.assertFalse(
520                 DefaultHostnameVerifier.matchIdentity(invalidHost, unicodeIdentity5),
521                 "Expected invalid host to not match"
522         );
523 
524         // Test 6: Invalid identity
525         final String unicodeHost4b = "пример.рф";
526         final String invalidIdentity = "xn--invalid-punycode";
527         Assertions.assertFalse(
528                 DefaultHostnameVerifier.matchIdentity(unicodeHost4b, invalidIdentity),
529                 "Expected invalid identity to not match"
530         );
531 
532         // Test 7: Mixed case comparison
533         final String unicodeHost5 = "ПрИмеР.рф";
534         final String unicodeIdentity6 = "пример.рф";
535         Assertions.assertTrue(
536                 DefaultHostnameVerifier.matchIdentity(unicodeHost5, unicodeIdentity6),
537                 "Expected case-insensitive Unicode comparison to match"
538         );
539 
540 
541         // Test 8: Wildcard in the middle label (per RFC 2818, should match)
542         final String unicodeHost6 = "sub.пример.рф";
543         final String unicodeIdentity8 = "sub.*.рф";
544         Assertions.assertTrue(
545                 DefaultHostnameVerifier.matchIdentity(unicodeHost6, unicodeIdentity8),
546                 "Expected wildcard in the middle label to match"
547         );
548     }
549 
550 }