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.http.conn.ssl;
29  
30  import java.io.ByteArrayInputStream;
31  import java.io.InputStream;
32  import java.security.Principal;
33  import java.security.cert.CertificateFactory;
34  import java.security.cert.X509Certificate;
35  import java.util.Arrays;
36  
37  import javax.net.ssl.SSLException;
38  
39  import org.junit.Assert;
40  import org.junit.Test;
41  import org.mockito.Mockito;
42  
43  /**
44   * Unit tests for {@link X509HostnameVerifier}.
45   */
46  public class TestHostnameVerifier {
47  
48      @Test
49      public void testVerify() throws Exception {
50          final X509HostnameVerifier DEFAULT = new BrowserCompatHostnameVerifier();
51          final X509HostnameVerifier STRICT = new StrictHostnameVerifier();
52          final X509HostnameVerifier ALLOW_ALL = new AllowAllHostnameVerifier();
53          final CertificateFactory cf = CertificateFactory.getInstance("X.509");
54          InputStream in;
55          X509Certificate x509;
56          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO);
57          x509 = (X509Certificate) cf.generateCertificate(in);
58  
59          DEFAULT.verify("foo.com", x509);
60          STRICT.verify("foo.com", x509);
61          exceptionPlease(DEFAULT, "a.foo.com", x509);
62          exceptionPlease(STRICT, "a.foo.com", x509);
63          exceptionPlease(DEFAULT, "bar.com", x509);
64          exceptionPlease(STRICT, "bar.com", x509);
65          ALLOW_ALL.verify("foo.com", x509);
66          ALLOW_ALL.verify("a.foo.com", x509);
67          ALLOW_ALL.verify("bar.com", x509);
68  
69          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_HANAKO);
70          x509 = (X509Certificate) cf.generateCertificate(in);
71          DEFAULT.verify("\u82b1\u5b50.co.jp", x509);
72          STRICT.verify("\u82b1\u5b50.co.jp", x509);
73          exceptionPlease(DEFAULT, "a.\u82b1\u5b50.co.jp", x509);
74          exceptionPlease(STRICT, "a.\u82b1\u5b50.co.jp", x509);
75  
76          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR);
77          x509 = (X509Certificate) cf.generateCertificate(in);
78          DEFAULT.verify("foo.com", x509);
79          STRICT.verify("foo.com", x509);
80          exceptionPlease(DEFAULT, "a.foo.com", x509);
81          exceptionPlease(STRICT, "a.foo.com", x509);
82          DEFAULT.verify("bar.com", x509);
83          STRICT.verify("bar.com", x509);
84          exceptionPlease(DEFAULT, "a.bar.com", x509);
85          exceptionPlease(STRICT, "a.bar.com", x509);
86  
87          in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR_HANAKO);
88          x509 = (X509Certificate) cf.generateCertificate(in);
89          DEFAULT.verify("foo.com", x509);
90          STRICT.verify("foo.com", x509);
91          exceptionPlease(DEFAULT, "a.foo.com", x509);
92          exceptionPlease(STRICT, "a.foo.com", x509);
93          DEFAULT.verify("bar.com", x509);
94          STRICT.verify("bar.com", x509);
95          exceptionPlease(DEFAULT, "a.bar.com", x509);
96          exceptionPlease(STRICT, "a.bar.com", x509);
97  
98          /*
99             Java isn't extracting international subjectAlts properly.  (Or
100            OpenSSL isn't storing them properly).
101         */
102         // DEFAULT.verify("\u82b1\u5b50.co.jp", x509 );
103         // STRICT.verify("\u82b1\u5b50.co.jp", x509 );
104         exceptionPlease(DEFAULT, "a.\u82b1\u5b50.co.jp", x509);
105         exceptionPlease(STRICT, "a.\u82b1\u5b50.co.jp", x509);
106 
107         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_NO_CNS_FOO);
108         x509 = (X509Certificate) cf.generateCertificate(in);
109         DEFAULT.verify("foo.com", x509);
110         STRICT.verify("foo.com", x509);
111         exceptionPlease(DEFAULT, "a.foo.com", x509);
112         exceptionPlease(STRICT, "a.foo.com", x509);
113 
114         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_NO_CNS_FOO);
115         x509 = (X509Certificate) cf.generateCertificate(in);
116         DEFAULT.verify("foo.com", x509);
117         STRICT.verify("foo.com", x509);
118         exceptionPlease(DEFAULT, "a.foo.com", x509);
119         exceptionPlease(STRICT, "a.foo.com", x509);
120 
121         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_THREE_CNS_FOO_BAR_HANAKO);
122         x509 = (X509Certificate) cf.generateCertificate(in);
123         exceptionPlease(DEFAULT, "foo.com", x509);
124         exceptionPlease(STRICT, "foo.com", x509);
125         exceptionPlease(DEFAULT, "a.foo.com", x509);
126         exceptionPlease(STRICT, "a.foo.com", x509);
127         exceptionPlease(DEFAULT, "bar.com", x509);
128         exceptionPlease(STRICT, "bar.com", x509);
129         exceptionPlease(DEFAULT, "a.bar.com", x509);
130         exceptionPlease(STRICT, "a.bar.com", x509);
131         DEFAULT.verify("\u82b1\u5b50.co.jp", x509);
132         STRICT.verify("\u82b1\u5b50.co.jp", x509);
133         exceptionPlease(DEFAULT, "a.\u82b1\u5b50.co.jp", x509);
134         exceptionPlease(STRICT, "a.\u82b1\u5b50.co.jp", x509);
135 
136         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_FOO);
137         x509 = (X509Certificate) cf.generateCertificate(in);
138         exceptionPlease(DEFAULT, "foo.com", x509);
139         exceptionPlease(STRICT, "foo.com", x509);
140         DEFAULT.verify("www.foo.com", x509);
141         STRICT.verify("www.foo.com", x509);
142         DEFAULT.verify("\u82b1\u5b50.foo.com", x509);
143         STRICT.verify("\u82b1\u5b50.foo.com", x509);
144         DEFAULT.verify("a.b.foo.com", x509);
145         exceptionPlease(STRICT, "a.b.foo.com", x509);
146 
147         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_CO_JP);
148         x509 = (X509Certificate) cf.generateCertificate(in);
149         // Silly test because no-one would ever be able to lookup an IP address
150         // using "*.co.jp".
151         DEFAULT.verify("*.co.jp", x509);
152         STRICT.verify("*.co.jp", x509);
153         DEFAULT.verify("foo.co.jp", x509);
154         exceptionPlease(STRICT, "foo.co.jp", x509);
155         DEFAULT.verify("\u82b1\u5b50.co.jp", x509);
156         exceptionPlease(STRICT, "\u82b1\u5b50.co.jp", x509);
157 
158         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_WILD_FOO_BAR_HANAKO);
159         x509 = (X509Certificate) cf.generateCertificate(in);
160         // try the foo.com variations
161         exceptionPlease(DEFAULT, "foo.com", x509);
162         exceptionPlease(STRICT, "foo.com", x509);
163         DEFAULT.verify("www.foo.com", x509);
164         STRICT.verify("www.foo.com", x509);
165         DEFAULT.verify("\u82b1\u5b50.foo.com", x509);
166         STRICT.verify("\u82b1\u5b50.foo.com", x509);
167         DEFAULT.verify("a.b.foo.com", x509);
168         exceptionPlease(STRICT, "a.b.foo.com", x509);
169         // try the bar.com variations
170         exceptionPlease(DEFAULT, "bar.com", x509);
171         exceptionPlease(STRICT, "bar.com", x509);
172         DEFAULT.verify("www.bar.com", x509);
173         STRICT.verify("www.bar.com", x509);
174         DEFAULT.verify("\u82b1\u5b50.bar.com", x509);
175         STRICT.verify("\u82b1\u5b50.bar.com", x509);
176         DEFAULT.verify("a.b.bar.com", x509);
177         exceptionPlease(STRICT, "a.b.bar.com", x509);
178         // try the \u82b1\u5b50.co.jp variations
179         /*
180            Java isn't extracting international subjectAlts properly.  (Or
181            OpenSSL isn't storing them properly).
182         */
183         //exceptionPlease( DEFAULT, "\u82b1\u5b50.co.jp", x509 );
184         //exceptionPlease( STRICT, "\u82b1\u5b50.co.jp", x509 );
185         //DEFAULT.verify("www.\u82b1\u5b50.co.jp", x509 );
186         //STRICT.verify("www.\u82b1\u5b50.co.jp", x509 );
187         //DEFAULT.verify("\u82b1\u5b50.\u82b1\u5b50.co.jp", x509 );
188         //STRICT.verify("\u82b1\u5b50.\u82b1\u5b50.co.jp", x509 );
189         //DEFAULT.verify("a.b.\u82b1\u5b50.co.jp", x509 );
190         //exceptionPlease(STRICT,"a.b.\u82b1\u5b50.co.jp", x509 );
191 
192         in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_VALUE_AVA);
193         x509 = (X509Certificate) cf.generateCertificate(in);
194         ALLOW_ALL.verify("repository.infonotary.com", x509);
195         DEFAULT.verify("repository.infonotary.com", x509);
196         STRICT.verify("repository.infonotary.com", x509);
197     }
198 
199     @Test
200     public void testSubjectAlt() throws Exception {
201         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
202         final InputStream in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_SUBJECT_ALT);
203         final X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);
204 
205         final X509HostnameVerifier verifier = SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
206 
207         Assert.assertEquals("CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CH",
208                 x509.getSubjectDN().getName());
209 
210         verifier.verify("localhost", x509);
211         verifier.verify("localhost.localdomain", x509);
212         verifier.verify("127.0.0.1", x509);
213 
214         try {
215             verifier.verify("local.host", x509);
216             Assert.fail("SSLException should have been thrown");
217         } catch (final SSLException ex) {
218             // expected
219         }
220         try {
221             verifier.verify("127.0.0.2", x509);
222             Assert.fail("SSLException should have been thrown");
223         } catch (final SSLException ex) {
224             // expected
225         }
226 
227     }
228 
229     public void exceptionPlease(final X509HostnameVerifier hv, final String host,
230                                  final X509Certificate x509) {
231         try {
232             hv.verify(host, x509);
233             Assert.fail("HostnameVerifier shouldn't allow [" + host + "]");
234         }
235         catch(final SSLException e) {
236             // whew!  we're okay!
237         }
238     }
239 
240     // Test helper method
241     private void checkMatching(final X509HostnameVerifier hv, final String host,
242             final String[] cns, final String[] alts, final boolean shouldFail) {
243         try {
244             hv.verify(host, cns, alts);
245             if (shouldFail) {
246                 Assert.fail("HostnameVerifier should not allow [" + host + "] to match "
247                         +Arrays.toString(cns)
248                         +" or "
249                         +Arrays.toString(alts));
250             }
251         }
252         catch(final SSLException e) {
253             if (!shouldFail) {
254                 Assert.fail("HostnameVerifier should have allowed [" + host + "] to match "
255                         +Arrays.toString(cns)
256                         +" or "
257                         +Arrays.toString(alts));
258             }
259         }
260     }
261 
262     @Test
263     // Check standard wildcard matching
264     public void testMatching() {
265         String cns[] = {};
266         String alt[] = {};
267         final X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier();
268         final X509HostnameVerifier shv = new StrictHostnameVerifier();
269         checkMatching(bhv, "a.b.c", cns, alt, true); // empty
270         checkMatching(shv, "a.b.c", cns, alt, true); // empty
271 
272         cns = new String []{"*.b.c"};
273         checkMatching(bhv, "a.b.c", cns, alt, false); // OK
274         checkMatching(shv, "a.b.c", cns, alt, false); // OK
275 
276         checkMatching(bhv, "s.a.b.c", cns, alt, false); // OK
277         checkMatching(shv, "s.a.b.c", cns, alt, true); // subdomain not OK
278 
279         cns = new String []{};
280         alt = new String []{"dummy", "*.b.c"}; // check matches against all alts
281         checkMatching(bhv, "a.b.c", cns, alt, false); // OK
282         checkMatching(shv, "a.b.c", cns, alt, false); // OK
283 
284         checkMatching(bhv, "s.a.b.c", cns, alt, false); // OK
285         checkMatching(shv, "s.a.b.c", cns, alt, true); // subdomain not OK
286 
287         alt = new String []{"*.gov.uk"};
288         checkMatching(bhv, "a.gov.uk", cns, alt, false); // OK
289         checkMatching(shv, "a.gov.uk", cns, alt, true); // Bad 2TLD
290 
291         checkMatching(bhv, "s.a.gov.uk", cns, alt, false); // OK
292         checkMatching(shv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD/no subdomain allowed
293 
294         alt = new String []{"*.gov.com"};
295         checkMatching(bhv, "a.gov.com", cns, alt, false); // OK, gov not 2TLD here
296         checkMatching(shv, "a.gov.com", cns, alt, false); // OK, gov not 2TLD here
297 
298         checkMatching(bhv, "s.a.gov.com", cns, alt, false); // OK, gov not 2TLD here
299         checkMatching(shv, "s.a.gov.com", cns, alt, true); // no subdomain allowed
300 
301         cns = new String []{"a*.gov.uk"}; // 2TLD check applies to wildcards
302         checkMatching(bhv, "a.gov.uk", cns, alt, false); // OK
303         checkMatching(shv, "a.gov.uk", cns, alt, true); // Bad 2TLD
304 
305         checkMatching(bhv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD
306         checkMatching(shv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD/no subdomain allowed
307 
308     }
309 
310     @Test
311     // Check compressed IPv6 hostname matching
312     public void testHTTPCLIENT_1316() throws Exception{
313         final String cns[] = {"2001:0db8:aaaa:bbbb:cccc:0:0:0001"};
314         final String alt[] = {};
315         final X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier();
316         final X509HostnameVerifier shv = new StrictHostnameVerifier();
317         checkMatching(bhv, "2001:0db8:aaaa:bbbb:cccc:0:0:0001", cns, alt, false);
318         checkMatching(shv, "2001:0db8:aaaa:bbbb:cccc:0:0:0001", cns, alt, false);
319         checkMatching(bhv, "2001:0db8:aaaa:bbbb:cccc::1", cns, alt, false);
320         checkMatching(shv, "2001:0db8:aaaa:bbbb:cccc::1", cns, alt, false);
321         checkMatching(bhv, "2001:0db8:aaaa:bbbb:cccc::10", cns, alt, true);
322         checkMatching(shv, "2001:0db8:aaaa:bbbb:cccc::10", cns, alt, true);
323         // TODO need some more samples
324     }
325 
326     @Test
327     public void testHTTPCLIENT_1097() {
328         String cns[];
329         final String alt[] = {};
330         final X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier();
331         final X509HostnameVerifier shv = new StrictHostnameVerifier();
332 
333         cns = new String []{"a*.b.c"}; // component part
334         checkMatching(bhv, "a.b.c", cns, alt, false); // OK
335         checkMatching(shv, "a.b.c", cns, alt, false); // OK
336 
337         checkMatching(bhv, "a.a.b.c", cns, alt, false); // OK
338         checkMatching(shv, "a.a.b.c", cns, alt, true); // subdomain not OK
339     }
340 
341     @Test
342     public void testHTTPCLIENT_1255() {
343         final X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier();
344         final X509HostnameVerifier shv = new StrictHostnameVerifier();
345 
346         final String cns[] = new String []{"m*.a.b.c.com"}; // component part
347         final String alt[] = {};
348         checkMatching(bhv, "mail.a.b.c.com", cns, alt, false); // OK
349         checkMatching(shv, "mail.a.b.c.com", cns, alt, false); // OK
350     }
351 
352     public void testGetCNs() {
353         final Principal principal = Mockito.mock(Principal.class);
354         final X509Certificate cert = Mockito.mock(X509Certificate.class);
355         Mockito.when(cert.getSubjectDN()).thenReturn(principal);
356         Mockito.when(principal.toString()).thenReturn("bla,  bla, blah");
357         Assert.assertArrayEquals(new String[] {}, AbstractVerifier.getCNs(cert));
358         Mockito.when(principal.toString()).thenReturn("Cn=,  Cn=  , CN, OU=CN=");
359         Assert.assertArrayEquals(new String[] {}, AbstractVerifier.getCNs(cert));
360         Mockito.when(principal.toString()).thenReturn("  Cn=blah,  CN= blah , OU=CN=yada");
361         Assert.assertArrayEquals(new String[] {"blah", " blah"}, AbstractVerifier.getCNs(cert));
362     }
363 
364 }