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.math.BigInteger;
35  import java.net.InetAddress;
36  import java.nio.charset.StandardCharsets;
37  import java.security.InvalidKeyException;
38  import java.security.NoSuchAlgorithmException;
39  import java.security.NoSuchProviderException;
40  import java.security.Principal;
41  import java.security.PublicKey;
42  import java.security.SignatureException;
43  import java.security.cert.CertificateEncodingException;
44  import java.security.cert.CertificateException;
45  import java.security.cert.CertificateExpiredException;
46  import java.security.cert.CertificateFactory;
47  import java.security.cert.CertificateNotYetValidException;
48  import java.security.cert.X509Certificate;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.Collection;
52  import java.util.Collections;
53  import java.util.Date;
54  import java.util.List;
55  import java.util.Set;
56  
57  import javax.net.ssl.SSLException;
58  
59  import org.apache.hc.client5.http.psl.PublicSuffixList;
60  import org.apache.hc.client5.http.psl.PublicSuffixListParser;
61  import org.apache.hc.client5.http.psl.PublicSuffixMatcher;
62  import org.apache.hc.client5.http.utils.DnsUtils;
63  import org.junit.jupiter.api.Assertions;
64  import org.junit.jupiter.api.BeforeEach;
65  import org.junit.jupiter.api.Test;
66  
67  /**
68   * Unit tests for {@link org.apache.hc.client5.http.ssl.DefaultHostnameVerifier}.
69   */
70  class TestDefaultHostnameVerifier {
71  
72      private DefaultHostnameVerifier impl;
73      private PublicSuffixMatcher publicSuffixMatcher;
74  
75      private static final String PUBLIC_SUFFIX_MATCHER_SOURCE_FILE = "suffixlistmatcher.txt";
76  
77      @BeforeEach
78      void setup() throws IOException {
79          impl = new DefaultHostnameVerifier();
80  
81          // Load the test PublicSuffixMatcher
82          final ClassLoader classLoader = getClass().getClassLoader();
83          final InputStream in = classLoader.getResourceAsStream(PUBLIC_SUFFIX_MATCHER_SOURCE_FILE);
84          Assertions.assertNotNull(in);
85          final List<PublicSuffixList> lists = PublicSuffixListParser.INSTANCE.parseByType(
86                  new InputStreamReader(in, StandardCharsets.UTF_8));
87          publicSuffixMatcher = new PublicSuffixMatcher(lists);
88      }
89  
90      @Test
91      void testVerify() throws Exception {
92          final CertificateFactory cf = CertificateFactory.getInstance("X.509");
93          InputStream in;
94          X509Certificate x509;
95          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO);
96          x509 = (X509Certificate) cf.generateCertificate(in);
97  
98          impl.verify("foo.com", x509);
99          exceptionPlease(impl, "a.foo.com", x509);
100         exceptionPlease(impl, "bar.com", x509);
101 
102         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_HANAKO);
103         x509 = (X509Certificate) cf.generateCertificate(in);
104         impl.verify("\u82b1\u5b50.co.jp", x509);
105         exceptionPlease(impl, "a.\u82b1\u5b50.co.jp", x509);
106 
107         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR);
108         x509 = (X509Certificate) cf.generateCertificate(in);
109         exceptionPlease(impl, "foo.com", x509);
110         exceptionPlease(impl, "a.foo.com", x509);
111         impl.verify("bar.com", x509);
112         exceptionPlease(impl, "a.bar.com", x509);
113 
114         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR_HANAKO);
115         x509 = (X509Certificate) cf.generateCertificate(in);
116         exceptionPlease(impl, "foo.com", x509);
117         exceptionPlease(impl, "a.foo.com", x509);
118         impl.verify("bar.com", x509);
119         exceptionPlease(impl, "a.bar.com", x509);
120 
121         /*
122            Java isn't extracting international subjectAlts properly.  (Or
123            OpenSSL isn't storing them properly).
124         */
125         // DEFAULT.verify("\u82b1\u5b50.co.jp", x509 );
126         // impl.verify("\u82b1\u5b50.co.jp", x509 );
127         exceptionPlease(impl, "a.\u82b1\u5b50.co.jp", x509);
128 
129         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_NO_CNS_FOO);
130         x509 = (X509Certificate) cf.generateCertificate(in);
131         impl.verify("foo.com", x509);
132         exceptionPlease(impl, "a.foo.com", x509);
133 
134         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_NO_CNS_FOO);
135         x509 = (X509Certificate) cf.generateCertificate(in);
136         impl.verify("foo.com", x509);
137         exceptionPlease(impl, "a.foo.com", x509);
138 
139         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_THREE_CNS_FOO_BAR_HANAKO);
140         x509 = (X509Certificate) cf.generateCertificate(in);
141         exceptionPlease(impl, "foo.com", x509);
142         exceptionPlease(impl, "a.foo.com", x509);
143         exceptionPlease(impl, "bar.com", x509);
144         exceptionPlease(impl, "a.bar.com", x509);
145         impl.verify("\u82b1\u5b50.co.jp", x509);
146         exceptionPlease(impl, "a.\u82b1\u5b50.co.jp", x509);
147 
148         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_FOO);
149         x509 = (X509Certificate) cf.generateCertificate(in);
150         exceptionPlease(impl, "foo.com", x509);
151         impl.verify("www.foo.com", x509);
152         impl.verify("\u82b1\u5b50.foo.com", x509);
153         exceptionPlease(impl, "a.b.foo.com", x509);
154 
155         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_CO_JP);
156         x509 = (X509Certificate) cf.generateCertificate(in);
157         // Silly test because no-one would ever be able to lookup an IP address
158         // using "*.co.jp".
159         impl.verify("*.co.jp", x509);
160         impl.verify("foo.co.jp", x509);
161         impl.verify("\u82b1\u5b50.co.jp", x509);
162 
163         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_FOO_BAR_HANAKO);
164         x509 = (X509Certificate) cf.generateCertificate(in);
165         // try the foo.com variations
166         exceptionPlease(impl, "foo.com", x509);
167         exceptionPlease(impl, "www.foo.com", x509);
168         exceptionPlease(impl, "\u82b1\u5b50.foo.com", x509);
169         exceptionPlease(impl, "a.b.foo.com", x509);
170         // try the bar.com variations
171         exceptionPlease(impl, "bar.com", x509);
172         impl.verify("www.bar.com", x509);
173         impl.verify("\u82b1\u5b50.bar.com", x509);
174         exceptionPlease(impl, "a.b.bar.com", x509);
175 
176         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_VALUE_AVA);
177         x509 = (X509Certificate) cf.generateCertificate(in);
178         impl.verify("repository.infonotary.com", x509);
179 
180         in = new ByteArrayInputStream(CertificatesToPlayWith.S_GOOGLE_COM);
181         x509 = (X509Certificate) cf.generateCertificate(in);
182         impl.verify("*.google.com", x509);
183 
184         in = new ByteArrayInputStream(CertificatesToPlayWith.S_GOOGLE_COM);
185         x509 = (X509Certificate) cf.generateCertificate(in);
186         impl.verify("*.Google.com", x509);
187 
188         in = new ByteArrayInputStream(CertificatesToPlayWith.IP_1_1_1_1);
189         x509 = (X509Certificate) cf.generateCertificate(in);
190         impl.verify("1.1.1.1", x509);
191         impl.verify("dummy-value.com", x509);
192 
193         exceptionPlease(impl, "1.1.1.2", x509);
194         exceptionPlease(impl, "not-the-cn.com", x509);
195 
196         in = new ByteArrayInputStream(CertificatesToPlayWith.EMAIL_ALT_SUBJECT_NAME);
197         x509 = (X509Certificate) cf.generateCertificate(in);
198         impl.verify("www.company.com", x509);
199     }
200 
201     @Test
202     void testSubjectAlt() throws Exception {
203         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
204         final InputStream in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_SUBJECT_ALT);
205         final X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);
206 
207         Assertions.assertEquals("CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CH",
208                 x509.getSubjectDN().getName());
209 
210         impl.verify("localhost.localdomain", x509);
211         impl.verify("127.0.0.1", x509);
212 
213         Assertions.assertThrows(SSLException.class, () -> impl.verify("localhost", x509));
214         Assertions.assertThrows(SSLException.class, () -> impl.verify("local.host", x509));
215         Assertions.assertThrows(SSLException.class, () -> impl.verify("127.0.0.2", x509));
216     }
217 
218     public void exceptionPlease(final DefaultHostnameVerifier hv, final String host,
219                                 final X509Certificate x509) {
220         Assertions.assertThrows(SSLException.class, () -> hv.verify(host, x509));
221     }
222 
223     @Test
224     void testParseFQDN() {
225         Assertions.assertEquals(Arrays.asList("blah"),
226                 DefaultHostnameVerifier.parseFQDN("blah"));
227         Assertions.assertEquals(Arrays.asList("blah", "blah"),
228                 DefaultHostnameVerifier.parseFQDN("blah.blah"));
229         Assertions.assertEquals(Arrays.asList("blah", "blah", "blah"),
230                 DefaultHostnameVerifier.parseFQDN("blah.blah.blah"));
231         Assertions.assertEquals(Arrays.asList("", "", "blah", ""),
232                 DefaultHostnameVerifier.parseFQDN(".blah.."));
233         Assertions.assertEquals(Arrays.asList(""),
234                 DefaultHostnameVerifier.parseFQDN(""));
235         Assertions.assertEquals(Arrays.asList("", ""),
236                 DefaultHostnameVerifier.parseFQDN("."));
237         Assertions.assertEquals(Arrays.asList("com", "domain", "host"),
238                 DefaultHostnameVerifier.parseFQDN("host.domain.com"));
239     }
240 
241     @Test
242     void testDomainRootMatching() {
243         Assertions.assertFalse(DefaultHostnameVerifier.matchDomainRoot("a.b.c", null));
244         Assertions.assertTrue(DefaultHostnameVerifier.matchDomainRoot("a.b.c", "a.b.c"));
245         Assertions.assertFalse(DefaultHostnameVerifier.matchDomainRoot("aa.b.c", "a.b.c"));
246         Assertions.assertFalse(DefaultHostnameVerifier.matchDomainRoot("a.b.c", "aa.b.c"));
247         Assertions.assertTrue(DefaultHostnameVerifier.matchDomainRoot("a.a.b.c", "a.b.c"));
248     }
249 
250     static boolean matchIdentity(final String host, final String identity,
251                                  final PublicSuffixMatcher publicSuffixMatcher, final boolean strict) {
252         return DefaultHostnameVerifier.matchIdentity(
253                 DnsUtils.normalizeUnicode(host),
254                 DnsUtils.normalizeUnicode(identity),
255                 publicSuffixMatcher, strict);
256     }
257 
258     static boolean matchIdentity(final String host, final String identity,
259                                  final PublicSuffixMatcher publicSuffixMatcher) {
260         return matchIdentity(host, identity, publicSuffixMatcher, false);
261     }
262 
263     static boolean matchIdentity(final String host, final String identity) {
264         return matchIdentity(host, identity, null, false);
265     }
266 
267     static boolean matchIdentityStrict(final String host, final String identity,
268                                        final PublicSuffixMatcher publicSuffixMatcher) {
269         return matchIdentity(host, identity, publicSuffixMatcher, true);
270     }
271 
272     static boolean matchIdentityStrict(final String host, final String identity) {
273         return matchIdentity(host, identity, null, true);
274     }
275 
276     @Test
277     void testIdentityMatching() {
278 
279         Assertions.assertTrue(matchIdentity("a.b.c", "*.b.c"));
280         Assertions.assertTrue(matchIdentityStrict("a.b.c", "*.b.c"));
281 
282         Assertions.assertTrue(matchIdentity("s.a.b.c", "*.b.c"));
283         Assertions.assertFalse(matchIdentityStrict("s.a.b.c", "*.b.c")); // subdomain not OK
284 
285         Assertions.assertTrue(matchIdentity("a.gov.uk", "*.gov.uk", publicSuffixMatcher));
286         Assertions.assertTrue(matchIdentityStrict("a.gov.uk", "*.gov.uk", publicSuffixMatcher));  // Bad 2TLD
287 
288         Assertions.assertTrue(matchIdentity("s.a.gov.uk", "*.a.gov.uk", publicSuffixMatcher));
289         Assertions.assertTrue(matchIdentityStrict("s.a.gov.uk", "*.a.gov.uk", publicSuffixMatcher));
290 
291         Assertions.assertTrue(matchIdentity("s.a.gov.uk", "*.gov.uk", publicSuffixMatcher));
292         Assertions.assertFalse(matchIdentityStrict("s.a.gov.uk", "*.gov.uk", publicSuffixMatcher));  // BBad 2TLD/no subdomain allowed
293 
294         Assertions.assertTrue(matchIdentity("a.gov.com", "*.gov.com", publicSuffixMatcher));
295         Assertions.assertTrue(matchIdentityStrict("a.gov.com", "*.gov.com", publicSuffixMatcher));
296 
297         Assertions.assertTrue(matchIdentity("s.a.gov.com", "*.gov.com", publicSuffixMatcher));
298         Assertions.assertFalse(matchIdentityStrict("s.a.gov.com", "*.gov.com", publicSuffixMatcher)); // no subdomain allowed
299 
300         Assertions.assertTrue(matchIdentity("a.gov.uk", "a*.gov.uk", publicSuffixMatcher));
301         Assertions.assertTrue(matchIdentityStrict("a.gov.uk", "a*.gov.uk", publicSuffixMatcher)); // Bad 2TLD
302 
303         Assertions.assertFalse(matchIdentity("s.a.gov.uk", "a*.gov.uk", publicSuffixMatcher)); // Bad 2TLD
304         Assertions.assertFalse(matchIdentityStrict("s.a.gov.uk", "a*.gov.uk", publicSuffixMatcher)); // Bad 2TLD/no subdomain allowed
305 
306         Assertions.assertFalse(matchIdentity("a.b.c", "*.b.*"));
307         Assertions.assertFalse(matchIdentityStrict("a.b.c", "*.b.*"));
308 
309         Assertions.assertFalse(matchIdentity("a.b.c", "*.*.c"));
310         Assertions.assertFalse(matchIdentityStrict("a.b.c", "*.*.c"));
311 
312         Assertions.assertTrue(matchIdentity("a.b.xxx.uk", "a.b.xxx.uk", publicSuffixMatcher));
313         Assertions.assertTrue(matchIdentityStrict("a.b.xxx.uk", "a.b.xxx.uk", publicSuffixMatcher));
314 
315         Assertions.assertTrue(matchIdentity("a.b.xxx.uk", "*.b.xxx.uk", publicSuffixMatcher));
316         Assertions.assertTrue(matchIdentityStrict("a.b.xxx.uk", "*.b.xxx.uk", publicSuffixMatcher));
317 
318         Assertions.assertTrue(matchIdentity("b.xxx.uk", "b.xxx.uk", publicSuffixMatcher));
319         Assertions.assertTrue(matchIdentityStrict("b.xxx.uk", "b.xxx.uk", publicSuffixMatcher));
320 
321         Assertions.assertTrue(matchIdentity("b.xxx.uk", "*.xxx.uk", publicSuffixMatcher));
322         Assertions.assertTrue(matchIdentityStrict("b.xxx.uk", "*.xxx.uk", publicSuffixMatcher));
323     }
324 
325     @Test
326     void testHTTPCLIENT_1097() {
327         Assertions.assertTrue(matchIdentity("a.b.c", "a*.b.c"));
328         Assertions.assertTrue(matchIdentityStrict("a.b.c", "a*.b.c"));
329 
330         Assertions.assertTrue(matchIdentity("a.a.b.c", "a*.b.c"));
331         Assertions.assertFalse(matchIdentityStrict("a.a.b.c", "a*.b.c"));
332     }
333 
334     @Test
335     void testHTTPCLIENT_1255() {
336         Assertions.assertTrue(matchIdentity("mail.a.b.c.com", "m*.a.b.c.com"));
337         Assertions.assertTrue(matchIdentityStrict("mail.a.b.c.com", "m*.a.b.c.com"));
338     }
339 
340     @Test
341     void testHTTPCLIENT_1997() {
342         String domain;
343         // Unknown
344         domain = "dev.b.cloud.a";
345         Assertions.assertTrue(matchIdentity("service.apps." + domain, "*.apps." + domain));
346         Assertions.assertTrue(matchIdentityStrict("service.apps." + domain, "*.apps." + domain));
347         Assertions.assertTrue(matchIdentity("service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
348         Assertions.assertTrue(matchIdentityStrict("service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
349 
350         // ICANN
351         domain = "dev.b.cloud.com";
352         Assertions.assertTrue(matchIdentity("service.apps." + domain, "*.apps." + domain));
353         Assertions.assertTrue(matchIdentityStrict("service.apps." + domain, "*.apps." + domain));
354         Assertions.assertTrue(matchIdentity("service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
355         Assertions.assertTrue(matchIdentityStrict("service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
356 
357         // PRIVATE
358         domain = "dev.b.cloud.lan";
359         Assertions.assertTrue(matchIdentity("service.apps." + domain, "*.apps." + domain));
360         Assertions.assertTrue(matchIdentityStrict("service.apps." + domain, "*.apps." + domain));
361         Assertions.assertTrue(matchIdentity("service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
362         Assertions.assertTrue(matchIdentityStrict("service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
363     }
364 
365     @Test // Check compressed IPv6 hostname matching
366     void testHTTPCLIENT_1316() throws Exception {
367         final String host1 = "2001:0db8:aaaa:bbbb:cccc:0:0:0001";
368         DefaultHostnameVerifier.matchIPv6Address(host1, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc:0:0:0001")));
369         DefaultHostnameVerifier.matchIPv6Address(host1, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::1")));
370         Assertions.assertThrows(SSLException.class, () ->
371                 DefaultHostnameVerifier.matchIPv6Address(host1, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::10"))));
372         final String host2 = "2001:0db8:aaaa:bbbb:cccc::1";
373         DefaultHostnameVerifier.matchIPv6Address(host2, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc:0:0:0001")));
374         DefaultHostnameVerifier.matchIPv6Address(host2, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::1")));
375         Assertions.assertThrows(SSLException.class, () ->
376                 DefaultHostnameVerifier.matchIPv6Address(host2, Collections.singletonList(SubjectName.IP("2001:0db8:aaaa:bbbb:cccc::10"))));
377     }
378 
379     @Test
380     void testHTTPCLIENT_2149() throws Exception {
381         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
382         final InputStream in = new ByteArrayInputStream(CertificatesToPlayWith.SUBJECT_ALT_IP_ONLY);
383         final X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);
384 
385         Assertions.assertEquals("CN=www.foo.com", x509.getSubjectDN().getName());
386 
387         impl.verify("127.0.0.1", x509);
388         impl.verify("www.foo.com", x509);
389 
390         exceptionPlease(impl, "127.0.0.2", x509);
391         exceptionPlease(impl, "www.bar.com", x509);
392     }
393 
394     @Test
395     void testExtractCN() throws Exception {
396         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("cn=blah, ou=blah, o=blah"));
397         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("cn=blah, cn=yada, cn=booh"));
398         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("c = pampa ,  cn  =    blah    , ou = blah , o = blah"));
399         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("cn=\"blah\", ou=blah, o=blah"));
400         Assertions.assertEquals("blah  blah", DefaultHostnameVerifier.extractCN("cn=\"blah  blah\", ou=blah, o=blah"));
401         Assertions.assertEquals("blah, blah", DefaultHostnameVerifier.extractCN("cn=\"blah, blah\", ou=blah, o=blah"));
402         Assertions.assertEquals("blah, blah", DefaultHostnameVerifier.extractCN("cn=blah\\, blah, ou=blah, o=blah"));
403         Assertions.assertEquals("blah", DefaultHostnameVerifier.extractCN("c = cn=uuh, cn=blah, ou=blah, o=blah"));
404         Assertions.assertThrows(SSLException.class, () ->
405                 DefaultHostnameVerifier.extractCN("blah,blah"));
406         Assertions.assertThrows(SSLException.class, () ->
407                 DefaultHostnameVerifier.extractCN("cn,o=blah"));
408     }
409 
410     @Test
411     void testMatchDNSName() throws Exception {
412         DefaultHostnameVerifier.matchDNSName(
413                 "host.domain.com",
414                 Collections.singletonList(SubjectName.DNS("*.domain.com")),
415                 publicSuffixMatcher);
416         DefaultHostnameVerifier.matchDNSName(
417                 "host.xx",
418                 Collections.singletonList(SubjectName.DNS("*.xx")),
419                 publicSuffixMatcher);
420         DefaultHostnameVerifier.matchDNSName(
421                 "host.appspot.com",
422                 Collections.singletonList(SubjectName.DNS("*.appspot.com")),
423                 publicSuffixMatcher);
424         DefaultHostnameVerifier.matchDNSName(
425                 "demo-s3-bucket.s3.eu-central-1.amazonaws.com",
426                 Collections.singletonList(SubjectName.DNS("*.s3.eu-central-1.amazonaws.com")),
427                 publicSuffixMatcher);
428         DefaultHostnameVerifier.matchDNSName(
429                 "hostname-workspace-1.local",
430                 Collections.singletonList(SubjectName.DNS("hostname-workspace-1.local")),
431                 publicSuffixMatcher);
432 
433         Assertions.assertThrows(SSLException.class, () ->
434                 DefaultHostnameVerifier.matchDNSName(
435                         "host.domain.com",
436                         Collections.singletonList(SubjectName.DNS("some.other.com")),
437                         publicSuffixMatcher));
438 
439         DefaultHostnameVerifier.matchDNSName(
440                 "host.ec2.compute-1.amazonaws.com",
441                 Collections.singletonList(SubjectName.DNS("host.ec2.compute-1.amazonaws.com")),
442                 publicSuffixMatcher);
443         DefaultHostnameVerifier.matchDNSName(
444                 "host.ec2.compute-1.amazonaws.com",
445                 Collections.singletonList(SubjectName.DNS("*.ec2.compute-1.amazonaws.com")),
446                 publicSuffixMatcher);
447         Assertions.assertThrows(SSLException.class, () ->
448             DefaultHostnameVerifier.matchDNSName(
449                     "compute-1.amazonaws.com",
450                     Collections.singletonList(SubjectName.DNS("*.compute-1.amazonaws.com")),
451                     publicSuffixMatcher));
452         DefaultHostnameVerifier.matchDNSName(
453                 "ec2.compute-1.amazonaws.com",
454                 Collections.singletonList(SubjectName.DNS("*.compute-1.amazonaws.com")),
455                 publicSuffixMatcher);
456     }
457 
458     @Test
459     void testMatchIdentity() {
460         // Test 1: IDN matching punycode
461         final String unicodeHost1 = "поиск-слов.рф";
462         final String punycodeHost1 = "xn----dtbqigoecuc.xn--p1ai";
463 
464         // These should now match, thanks to IDN.toASCII():
465         Assertions.assertTrue(matchIdentity(unicodeHost1, punycodeHost1),
466                 "Expected the Unicode host and its punycode to match"
467         );
468 
469         // ‘example.com’ vs. an unrelated punycode domain should fail:
470         Assertions.assertFalse(
471                 matchIdentity("example.com", punycodeHost1),
472                 "Expected mismatch between example.com and xn----dtbqigoecuc.xn--p1ai"
473         );
474 
475         // Test 2: Unicode host and Unicode identity
476         final String unicodeHost2 = "пример.рф";
477         final String unicodeIdentity2 = "пример.рф";
478         Assertions.assertTrue(matchIdentity(unicodeHost2, unicodeIdentity2),
479                 "Expected Unicode host and Unicode identity to match"
480         );
481 
482         // Test 3: Punycode host and Unicode identity
483         final String unicodeHost3 = "пример.рф";
484         final String punycodeIdentity3 = "xn--e1afmkfd.xn--p1ai";
485         Assertions.assertTrue(matchIdentity(unicodeHost3, punycodeIdentity3),
486                 "Expected Unicode host and punycode identity to match"
487         );
488 
489         // Test 4: Wildcard matching in the left-most label
490         final String unicodeHost4 = "sub.пример.рф";
491         final String unicodeIdentity4 = "*.пример.рф";
492         Assertions.assertTrue(matchIdentity(unicodeHost4, unicodeIdentity4),
493                 "Expected wildcard to match subdomain"
494         );
495 
496         // Test 5: Invalid host
497         final String invalidHost = "invalid_host";
498         final String unicodeIdentity5 = "пример.рф";
499         Assertions.assertFalse(
500                 matchIdentity(invalidHost, unicodeIdentity5),
501                 "Expected invalid host to not match"
502         );
503 
504         // Test 6: Invalid identity
505         final String unicodeHost4b = "пример.рф";
506         final String invalidIdentity = "xn--invalid-punycode";
507         Assertions.assertFalse(
508                 matchIdentity(unicodeHost4b, invalidIdentity),
509                 "Expected invalid identity to not match"
510         );
511 
512         // Test 7: Mixed case comparison
513         final String unicodeHost5 = "ПрИмеР.рф";
514         final String unicodeIdentity6 = "пример.рф";
515         Assertions.assertTrue(matchIdentity(unicodeHost5, unicodeIdentity6),
516                 "Expected case-insensitive Unicode comparison to match"
517         );
518 
519 
520         // Test 8: Wildcard in the middle label (per RFC 2818, should match)
521         final String unicodeHost6 = "sub.пример.рф";
522         final String unicodeIdentity8 = "sub.*.рф";
523         Assertions.assertTrue(matchIdentity(unicodeHost6, unicodeIdentity8),
524                 "Expected wildcard in the middle label to match"
525         );
526     }
527 
528 
529     @Test
530     void testSimulatedByteProperties() throws Exception {
531         // Simulated byte array for an IP address
532         final byte[] ipAsByteArray = {1, 1, 1, 1}; // 1.1.1.1 in byte form
533 
534         final List<List<?>> entries = new ArrayList<>();
535         final List<Object> entry = new ArrayList<>();
536         entry.add(SubjectName.IP);
537         entry.add(ipAsByteArray);
538         entries.add(entry);
539 
540         // Mocking the certificate behavior
541         final X509Certificate mockCert = generateX509Certificate(entries);
542 
543         final List<SubjectName> result = DefaultHostnameVerifier.getSubjectAltNames(mockCert, -1);
544         Assertions.assertEquals(1, result.size(), "Should have one SubjectAltName");
545 
546         final SubjectName sn = result.get(0);
547         Assertions.assertEquals(SubjectName.IP, sn.getType(), "Should be an IP type");
548         // Here, you'll need logic to convert byte array to string for assertion
549         Assertions.assertEquals("1.1.1.1", sn.getValue(), "IP address should match after conversion");
550     }
551 
552     @Test
553     void testSimulatedBytePropertiesIPv6() throws Exception {
554         final byte[] ipv6AsByteArray = InetAddress.getByName("2001:db8:85a3::8a2e:370:7334").getAddress();
555         // IPv6 2001:db8:85a3::8a2e:370:7334
556 
557         final List<List<?>> entries = new ArrayList<>();
558         final List<Object> entry = new ArrayList<>();
559         entry.add(SubjectName.IP);
560         entry.add(ipv6AsByteArray);
561         entries.add(entry);
562 
563         // Mocking the certificate behavior
564         final X509Certificate mockCert = generateX509Certificate(entries);
565 
566         final List<SubjectName> result = DefaultHostnameVerifier.getSubjectAltNames(mockCert, -1);
567         Assertions.assertEquals(1, result.size(), "Should have one SubjectAltName");
568 
569         final SubjectName sn = result.get(0);
570         Assertions.assertEquals(SubjectName.IP, sn.getType(), "Should be an IP type");
571         // Here, you'll need logic to convert byte array to string for assertion
572         Assertions.assertEquals("2001:0db8:85a3:0000:0000:8a2e:0370:7334", sn.getValue(), "IP address should match after conversion");
573     }
574 
575 
576     private X509Certificate generateX509Certificate(final List<List<?>> entries) {
577         return new X509Certificate() {
578 
579             @Override
580             public boolean hasUnsupportedCriticalExtension() {
581                 return false;
582             }
583 
584             @Override
585             public Set<String> getCriticalExtensionOIDs() {
586                 return null;
587             }
588 
589             @Override
590             public Set<String> getNonCriticalExtensionOIDs() {
591                 return null;
592             }
593 
594             @Override
595             public byte[] getExtensionValue(final String oid) {
596                 return new byte[0];
597             }
598 
599             @Override
600             public byte[] getEncoded() throws CertificateEncodingException {
601                 return new byte[0];
602             }
603 
604             @Override
605             public void verify(final PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
606 
607             }
608 
609             @Override
610             public void verify(final PublicKey key, final String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
611 
612             }
613 
614             @Override
615             public String toString() {
616                 return "";
617             }
618 
619             @Override
620             public PublicKey getPublicKey() {
621                 return null;
622             }
623 
624             @Override
625             public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {
626 
627             }
628 
629             @Override
630             public void checkValidity(final Date date) throws CertificateExpiredException, CertificateNotYetValidException {
631 
632             }
633 
634             @Override
635             public int getVersion() {
636                 return 0;
637             }
638 
639             @Override
640             public BigInteger getSerialNumber() {
641                 return null;
642             }
643 
644             @Override
645             public Principal getIssuerDN() {
646                 return null;
647             }
648 
649             @Override
650             public Principal getSubjectDN() {
651                 return null;
652             }
653 
654             @Override
655             public Date getNotBefore() {
656                 return null;
657             }
658 
659             @Override
660             public Date getNotAfter() {
661                 return null;
662             }
663 
664             @Override
665             public byte[] getTBSCertificate() throws CertificateEncodingException {
666                 return new byte[0];
667             }
668 
669             @Override
670             public byte[] getSignature() {
671                 return new byte[0];
672             }
673 
674             @Override
675             public String getSigAlgName() {
676                 return "";
677             }
678 
679             @Override
680             public String getSigAlgOID() {
681                 return "";
682             }
683 
684             @Override
685             public byte[] getSigAlgParams() {
686                 return new byte[0];
687             }
688 
689             @Override
690             public boolean[] getIssuerUniqueID() {
691                 return new boolean[0];
692             }
693 
694             @Override
695             public boolean[] getSubjectUniqueID() {
696                 return new boolean[0];
697             }
698 
699             @Override
700             public boolean[] getKeyUsage() {
701                 return new boolean[0];
702             }
703 
704             @Override
705             public int getBasicConstraints() {
706                 return 0;
707             }
708 
709             @Override
710             public Collection<List<?>> getSubjectAlternativeNames() {
711                 return entries;
712             }
713         };
714 
715     }
716 
717 }