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.core5.ssl;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.net.InetSocketAddress;
34  import java.net.ServerSocket;
35  import java.net.Socket;
36  import java.net.URL;
37  import java.security.KeyStore;
38  import java.security.KeyStoreException;
39  import java.security.NoSuchAlgorithmException;
40  import java.security.NoSuchProviderException;
41  import java.security.Principal;
42  import java.security.Security;
43  import java.security.UnrecoverableKeyException;
44  import java.security.cert.X509Certificate;
45  import java.util.Arrays;
46  import java.util.LinkedHashSet;
47  import java.util.Set;
48  import java.util.concurrent.ExecutorService;
49  import java.util.concurrent.Executors;
50  import java.util.concurrent.Future;
51  import java.util.concurrent.TimeUnit;
52  import java.util.concurrent.atomic.AtomicReference;
53  
54  import javax.net.ssl.KeyManagerFactory;
55  import javax.net.ssl.SSLContext;
56  import javax.net.ssl.SSLException;
57  import javax.net.ssl.SSLParameters;
58  import javax.net.ssl.SSLPeerUnverifiedException;
59  import javax.net.ssl.SSLServerSocket;
60  import javax.net.ssl.SSLSession;
61  import javax.net.ssl.SSLSocket;
62  import javax.net.ssl.TrustManagerFactory;
63  
64  import org.apache.hc.core5.util.Timeout;
65  import org.junit.jupiter.api.AfterEach;
66  import org.junit.jupiter.api.Assertions;
67  import org.junit.jupiter.api.Test;
68  
69  /**
70   * Unit tests for {@link SSLContextBuilder}.
71   */
72  public class TestSSLContextBuilder {
73  
74      static final String PROVIDER_SUN_JSSE = "SunJSSE";
75      static final String PROVIDER_SUN_JCE = "SunJCE";
76  
77      private static boolean isWindows() {
78          return System.getProperty("os.name").contains("Windows");
79      }
80  
81      private static final Timeout TIMEOUT = Timeout.ofSeconds(5);
82      private ExecutorService executorService;
83  
84      @AfterEach
85      public void cleanup() throws Exception {
86          if (this.executorService != null) {
87              this.executorService.shutdown();
88              this.executorService.awaitTermination(5, TimeUnit.SECONDS);
89          }
90      }
91  
92      private URL getResource(final String name) {
93          return getClass().getResource(name);
94      }
95  
96      @Test
97      public void testBuildAllDefaults() throws Exception {
98          final SSLContext sslContext = SSLContextBuilder.create()
99                  .setKeyStoreType(KeyStore.getDefaultType())
100                 .setKeyManagerFactoryAlgorithm(KeyManagerFactory.getDefaultAlgorithm())
101                 .setTrustManagerFactoryAlgorithm(TrustManagerFactory.getDefaultAlgorithm())
102                 .setProvider(PROVIDER_SUN_JSSE)
103                 .setProtocol("TLS")
104                 .setSecureRandom(null)
105                 .loadTrustMaterial((KeyStore) null, null)
106                 .loadKeyMaterial((KeyStore) null, null, null)
107                 .build();
108         Assertions.assertNotNull(sslContext);
109         Assertions.assertEquals("TLS", sslContext.getProtocol());
110         Assertions.assertEquals(PROVIDER_SUN_JSSE, sslContext.getProvider().getName());
111     }
112 
113     @Test
114     public void testBuildAllNull() throws Exception {
115         final SSLContext sslContext = SSLContextBuilder.create()
116                 .setKeyStoreType(null)
117                 .setKeyManagerFactoryAlgorithm(null)
118                 .setTrustManagerFactoryAlgorithm(null)
119                 .setProtocol(null)
120                 .setProvider((String) null)
121                 .setSecureRandom(null)
122                 .loadTrustMaterial((KeyStore) null, null)
123                 .loadKeyMaterial((KeyStore) null, null, null)
124                 .build();
125         Assertions.assertNotNull(sslContext);
126         Assertions.assertEquals("TLS", sslContext.getProtocol());
127         Assertions.assertEquals(PROVIDER_SUN_JSSE, sslContext.getProvider().getName());
128     }
129 
130     @Test
131     public void testBuildAllNull_deprecated() throws Exception {
132         final SSLContext sslContext = SSLContextBuilder.create()
133                 .setProtocol(null)
134                 .setSecureRandom(null)
135                 .loadTrustMaterial((KeyStore) null, null)
136                 .loadKeyMaterial((KeyStore) null, null, null)
137                 .build();
138         Assertions.assertNotNull(sslContext);
139         Assertions.assertEquals("TLS", sslContext.getProtocol());
140     }
141 
142     @Test
143     public void testBuildDefault() throws Exception {
144         new SSLContextBuilder().build();
145     }
146 
147     @Test
148     public void testBuildNoSuchKeyManagerFactoryAlgorithm() throws Exception {
149         final URL resource1 = getResource("/test-keypasswd.p12");
150         final String storePassword = "nopassword";
151         final String keyPassword = "password";
152         Assertions.assertThrows(NoSuchAlgorithmException.class, () ->
153                 SSLContextBuilder.create()
154                         .setKeyManagerFactoryAlgorithm(" BAD ")
155                         .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
156                         .build());
157     }
158 
159     @Test
160     public void testBuildNoSuchKeyStoreType() throws Exception {
161         final URL resource1 = getResource("/test-keypasswd.p12");
162         final String storePassword = "nopassword";
163         final String keyPassword = "password";
164         Assertions.assertThrows(KeyStoreException.class, () ->
165                 SSLContextBuilder.create()
166                         .setKeyStoreType(" BAD ")
167                         .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
168                         .build());
169     }
170 
171     @Test
172     public void testBuildNoSuchTrustManagerFactoryAlgorithm() throws Exception {
173         final URL resource1 = getResource("/test-keypasswd.p12");
174         final String storePassword = "nopassword";
175         Assertions.assertThrows(NoSuchAlgorithmException.class, () ->
176                 SSLContextBuilder.create()
177                         .setTrustManagerFactoryAlgorithm(" BAD ")
178                         .loadTrustMaterial(resource1, storePassword.toCharArray())
179                         .build());
180     }
181 
182     @Test
183     public void testBuildWithProvider() throws Exception {
184         final URL resource1 = getResource("/test-server.p12");
185         final String storePassword = "nopassword";
186         final String keyPassword = "nopassword";
187         final DummyProvider provider = new DummyProvider();
188         SSLContextBuilder.create()
189                 .setProvider(provider)
190                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
191                 .build();
192         Assertions.assertTrue(provider.hasBeenRequested("SSLContext"));
193     }
194 
195     @Test
196     public void testBuildWithProviderName() throws Exception {
197 
198         final DummyProvider provider = new DummyProvider();
199         Security.insertProviderAt(provider, 1);
200         try {
201 
202             final URL resource1 = getResource("/test-server.p12");
203             final String storePassword = "nopassword";
204             final String keyPassword = "nopassword";
205             SSLContextBuilder.create()
206                     .setProvider(DummyProvider.NAME)
207                     .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
208                     .build();
209             Assertions.assertTrue(provider.hasBeenRequested("SSLContext"));
210 
211         } finally {
212             Security.removeProvider(DummyProvider.NAME);
213         }
214     }
215 
216     @Test
217     public void testBuildKSWithNoSuchProvider() {
218         Assertions.assertThrows(NoSuchProviderException.class,
219                 () -> SSLContextBuilder.create()
220                 .setKeyStoreProvider("no-such-provider")
221                 .build());
222     }
223 
224     @Test
225     public void testBuildKSWithProvider() throws Exception {
226         final URL resource1 = getResource("/test-server.p12");
227         final String storePassword = "nopassword";
228         final String keyPassword = "nopassword";
229         final DummyProvider provider = new DummyProvider();
230         SSLContextBuilder.create()
231                 .setKeyStoreProvider(provider)
232                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
233                 .build();
234         Assertions.assertTrue(provider.hasBeenRequested("KeyManagerFactory"));
235     }
236 
237     @Test
238     public void testBuildKSWithProviderName() throws Exception {
239 
240         final DummyProvider provider = new DummyProvider();
241         Security.insertProviderAt(provider, 1);
242         try {
243 
244             final URL resource1 = getResource("/test-server.p12");
245             final String storePassword = "nopassword";
246             final String keyPassword = "nopassword";
247             SSLContextBuilder.create()
248                     .setKeyStoreProvider(DummyProvider.NAME)
249                     .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
250                     .build();
251             Assertions.assertTrue(provider.hasBeenRequested("KeyManagerFactory"));
252 
253         } finally {
254             Security.removeProvider(DummyProvider.NAME);
255         }
256     }
257 
258     @Test
259     public void testBuildTSWithNoSuchProvider() {
260         Assertions.assertThrows(NoSuchProviderException.class, ()->
261             SSLContextBuilder.create()
262                     .setTrustStoreProvider("no-such-provider")
263                     .build());
264     }
265 
266     @Test
267     public void testBuildTSWithProvider() throws Exception {
268         final DummyProvider provider = new DummyProvider();
269         SSLContextBuilder.create()
270                 .setTrustStoreProvider(provider)
271                 .loadTrustMaterial((KeyStore) null, null)
272                 .build();
273         Assertions.assertTrue(provider.hasBeenRequested("TrustManagerFactory"));
274     }
275 
276     @Test
277     public void testBuildTSWithProviderName() throws Exception {
278 
279         final DummyProvider provider = new DummyProvider();
280         Security.insertProviderAt(provider, 1);
281         try {
282 
283             SSLContextBuilder.create()
284                     .setTrustStoreProvider(DummyProvider.NAME)
285                     .loadTrustMaterial((KeyStore) null, null)
286                     .build();
287             Assertions.assertTrue(provider.hasBeenRequested("TrustManagerFactory"));
288 
289         } finally {
290             Security.removeProvider(DummyProvider.NAME);
291         }
292     }
293 
294 
295     @Test
296     public void testKeyWithAlternatePasswordInvalid() throws Exception {
297         final URL resource1 = getResource("/test-keypasswd.p12");
298         final String storePassword = "nopassword";
299         final String keyPassword = "!password";
300         Assertions.assertThrows(UnrecoverableKeyException.class, () ->
301                 SSLContextBuilder.create()
302                         .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
303                         .loadTrustMaterial(resource1, storePassword.toCharArray())
304                         .build());
305     }
306 
307     @Test
308     public void testSSLHandshakeServerTrusted() throws Exception {
309         final URL resource1 = getResource("/test.p12");
310         final String storePassword = "nopassword";
311         final String keyPassword = "nopassword";
312         final SSLContext serverSslContext = SSLContextBuilder.create()
313                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
314                 .build();
315         Assertions.assertNotNull(serverSslContext);
316         final SSLContext clientSslContext = SSLContextBuilder.create()
317                 .loadTrustMaterial(resource1, storePassword.toCharArray())
318                 .build();
319         Assertions.assertNotNull(clientSslContext);
320         final ServerSocket serverSocket = serverSslContext.getServerSocketFactory().createServerSocket();
321         serverSocket.bind(new InetSocketAddress(0));
322 
323         this.executorService = Executors.newSingleThreadExecutor();
324         final Future<Boolean> future = this.executorService.submit(() -> {
325             try (Socket socket = serverSocket.accept()) {
326                 final OutputStream outputStream = socket.getOutputStream();
327                 outputStream.write(new byte[]{'H', 'i'});
328                 outputStream.flush();
329             }
330             return Boolean.TRUE;
331         });
332 
333         final int localPort = serverSocket.getLocalPort();
334         try (final Socket clientSocket = clientSslContext.getSocketFactory().createSocket()) {
335             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
336             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
337             final InputStream inputStream = clientSocket.getInputStream();
338             Assertions.assertEquals('H', inputStream.read());
339             Assertions.assertEquals('i', inputStream.read());
340             Assertions.assertEquals(-1, inputStream.read());
341         }
342 
343         final Boolean result = future.get(5, TimeUnit.SECONDS);
344         Assertions.assertNotNull(result);
345     }
346 
347     @Test
348     public void testSSLHandshakeServerNotTrusted() throws Exception {
349         final URL resource1 = getResource("/test-server.p12");
350         final String storePassword = "nopassword";
351         final String keyPassword = "nopassword";
352         final SSLContext serverSslContext = SSLContextBuilder.create()
353                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
354                 .build();
355         Assertions.assertNotNull(serverSslContext);
356         final URL resource2 = getResource("/test.p12");
357         final SSLContext clientSslContext = SSLContextBuilder.create()
358                 .loadTrustMaterial(resource2, storePassword.toCharArray())
359                 .build();
360         Assertions.assertNotNull(clientSslContext);
361         final ServerSocket serverSocket = serverSslContext.getServerSocketFactory().createServerSocket();
362         serverSocket.bind(new InetSocketAddress(0));
363 
364         this.executorService = Executors.newSingleThreadExecutor();
365         this.executorService.submit(() -> {
366             try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
367                 socket.getSession();
368             }
369             return Boolean.FALSE;
370         });
371         final int localPort = serverSocket.getLocalPort();
372         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
373             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
374             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
375             Assertions.assertThrows(IOException.class, clientSocket::startHandshake);
376         }
377     }
378 
379     @Test
380     public void testSSLHandshakeServerCustomTrustStrategy() throws Exception {
381         final URL resource1 = getResource("/test-server.p12");
382         final String storePassword = "nopassword";
383         final String keyPassword = "nopassword";
384         final SSLContext serverSslContext = SSLContextBuilder.create()
385                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
386                 .build();
387         Assertions.assertNotNull(serverSslContext);
388 
389         final AtomicReference<X509Certificate[]> certChainRef = new AtomicReference<>();
390 
391         final TrustStrategy trustStrategy = (chain, authType) -> {
392             certChainRef.set(chain);
393             return true;
394         };
395 
396         final SSLContext clientSslContext = SSLContextBuilder.create()
397                 .loadTrustMaterial(trustStrategy)
398                 .build();
399 
400         Assertions.assertNotNull(clientSslContext);
401         final ServerSocket serverSocket = serverSslContext.getServerSocketFactory().createServerSocket();
402         serverSocket.bind(new InetSocketAddress(0));
403 
404         this.executorService = Executors.newSingleThreadExecutor();
405         final Future<Boolean> future = this.executorService.submit(() -> {
406             try (Socket socket = serverSocket.accept()) {
407                 final OutputStream outputStream = socket.getOutputStream();
408                 outputStream.write(new byte[]{'H', 'i'});
409                 outputStream.flush();
410             }
411             return Boolean.TRUE;
412         });
413 
414         final int localPort = serverSocket.getLocalPort();
415         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
416             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
417             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
418             final InputStream inputStream = clientSocket.getInputStream();
419             Assertions.assertEquals('H', inputStream.read());
420             Assertions.assertEquals('i', inputStream.read());
421             Assertions.assertEquals(-1, inputStream.read());
422         }
423 
424         final Boolean result = future.get(5, TimeUnit.SECONDS);
425         Assertions.assertNotNull(result);
426 
427         final X509Certificate[] certs = certChainRef.get();
428         Assertions.assertNotNull(certs);
429         Assertions.assertEquals(2, certs.length);
430         final X509Certificate cert1 = certs[0];
431         final Principal subjectDN1 = cert1.getSubjectDN();
432         Assertions.assertNotNull(subjectDN1);
433         Assertions.assertEquals("CN=Test Server, OU=HttpComponents Project, O=Apache Software Foundation", subjectDN1.getName());
434         final X509Certificate cert2 = certs[1];
435         final Principal subjectDN2 = cert2.getSubjectDN();
436         Assertions.assertNotNull(subjectDN2);
437         Assertions.assertEquals("EMAILADDRESS=dev@hc.apache.org, " +
438                 "CN=Test CA, OU=HttpComponents Project, O=Apache Software Foundation", subjectDN2.getName());
439         final Principal issuerDN = cert2.getIssuerDN();
440         Assertions.assertNotNull(issuerDN);
441         Assertions.assertEquals("EMAILADDRESS=dev@hc.apache.org, " +
442                 "CN=Test CA, OU=HttpComponents Project, O=Apache Software Foundation", issuerDN.getName());
443 
444     }
445 
446     @Test
447     public void testSSLHandshakeClientUnauthenticated() throws Exception {
448         final URL resource1 = getResource("/test-server.p12");
449         final String storePassword = "nopassword";
450         final String keyPassword = "nopassword";
451         final SSLContext serverSslContext = SSLContextBuilder.create()
452                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
453                 .build();
454         Assertions.assertNotNull(serverSslContext);
455         final URL resource2 = getResource("/test-client.p12");
456         final SSLContext clientSslContext = SSLContextBuilder.create()
457                 .loadTrustMaterial(resource2, storePassword.toCharArray())
458                 .build();
459         Assertions.assertNotNull(clientSslContext);
460         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
461         serverSocket.setWantClientAuth(true);
462         serverSocket.bind(new InetSocketAddress(0));
463 
464         this.executorService = Executors.newSingleThreadExecutor();
465         final Future<Principal> future = this.executorService.submit(() -> {
466             final SSLSocket socket = (SSLSocket) serverSocket.accept();
467             Principal clientPrincipal = null;
468             try {
469                 final SSLSession session = socket.getSession();
470                 try {
471                     clientPrincipal = session.getPeerPrincipal();
472                 } catch (final SSLPeerUnverifiedException ignore) {
473                 }
474                 final OutputStream outputStream = socket.getOutputStream();
475                 outputStream.write(new byte [] {'H', 'i'});
476                 outputStream.flush();
477             } finally {
478                 socket.close();
479             }
480             return clientPrincipal;
481         });
482 
483         final int localPort = serverSocket.getLocalPort();
484         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
485             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
486             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
487             clientSocket.startHandshake();
488             final InputStream inputStream = clientSocket.getInputStream();
489             Assertions.assertEquals('H', inputStream.read());
490             Assertions.assertEquals('i', inputStream.read());
491             Assertions.assertEquals(-1, inputStream.read());
492         }
493 
494         final Principal clientPrincipal = future.get(5, TimeUnit.SECONDS);
495         Assertions.assertNull(clientPrincipal);
496     }
497 
498     @Test
499     public void testSSLHandshakeClientUnauthenticatedError() throws Exception {
500         final URL resource1 = getResource("/test-server.p12");
501         final String storePassword = "nopassword";
502         final String keyPassword = "nopassword";
503         final SSLContext serverSslContext = SSLContextBuilder.create()
504                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
505                 .build();
506         Assertions.assertNotNull(serverSslContext);
507         final URL resource2 = getResource("/test-client.p12");
508         final SSLContext clientSslContext = SSLContextBuilder.create()
509                 .loadTrustMaterial(resource2, storePassword.toCharArray())
510                 .build();
511         Assertions.assertNotNull(clientSslContext);
512         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
513         serverSocket.setNeedClientAuth(true);
514         serverSocket.bind(new InetSocketAddress(0));
515 
516         this.executorService = Executors.newSingleThreadExecutor();
517         this.executorService.submit(() -> {
518             try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
519                 socket.getSession();
520             }
521             return Boolean.FALSE;
522         });
523 
524         final int localPort = serverSocket.getLocalPort();
525         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
526             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
527             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
528             Assertions.assertThrows(IOException.class, () -> {
529                 clientSocket.startHandshake();
530                 final InputStream inputStream = clientSocket.getInputStream();
531                 inputStream.read();
532             });
533         }
534     }
535 
536     @Test
537     public void testSSLHandshakeClientAuthenticated() throws Exception {
538         final URL resource1 = getResource("/test-server.p12");
539         final String storePassword = "nopassword";
540         final String keyPassword = "nopassword";
541         final SSLContext serverSslContext = SSLContextBuilder.create()
542                 .loadTrustMaterial(resource1, storePassword.toCharArray())
543                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
544                 .build();
545         Assertions.assertNotNull(serverSslContext);
546         final URL resource2 = getResource("/test-client.p12");
547         final SSLContext clientSslContext = SSLContextBuilder.create()
548                 .loadTrustMaterial(resource2, storePassword.toCharArray())
549                 .loadKeyMaterial(resource2, storePassword.toCharArray(), storePassword.toCharArray())
550                 .build();
551         Assertions.assertNotNull(clientSslContext);
552         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
553         serverSocket.setNeedClientAuth(true);
554         serverSocket.bind(new InetSocketAddress(0));
555 
556         this.executorService = Executors.newSingleThreadExecutor();
557         final Future<Principal> future = this.executorService.submit(() -> {
558             try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
559                 final SSLSession session = socket.getSession();
560                 final Principal clientPrincipal = session.getPeerPrincipal();
561                 final OutputStream outputStream = socket.getOutputStream();
562                 outputStream.write(new byte[]{'H', 'i'});
563                 outputStream.flush();
564                 return clientPrincipal;
565             }
566         });
567         final int localPort = serverSocket.getLocalPort();
568         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
569             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
570             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
571             clientSocket.startHandshake();
572             final InputStream inputStream = clientSocket.getInputStream();
573             Assertions.assertEquals('H', inputStream.read());
574             Assertions.assertEquals('i', inputStream.read());
575             Assertions.assertEquals(-1, inputStream.read());
576         }
577 
578         final Principal clientPrincipal = future.get(5, TimeUnit.SECONDS);
579         Assertions.assertNotNull(clientPrincipal);
580     }
581 
582     @Test
583     public void testSSLHandshakeClientAuthenticatedPrivateKeyStrategy() throws Exception {
584         final URL resource1 = getResource("/test-server.p12");
585         final String storePassword = "nopassword";
586         final String keyPassword = "nopassword";
587         final SSLContext serverSslContext = SSLContextBuilder.create()
588                 .loadTrustMaterial(resource1, storePassword.toCharArray())
589                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
590                 .build();
591         Assertions.assertNotNull(serverSslContext);
592 
593         final PrivateKeyStrategy privateKeyStrategy = (aliases, sslParameters) -> aliases.containsKey("client2") ? "client2" : null;
594 
595         final URL resource2 = getResource("/test-client.p12");
596         final SSLContext clientSslContext = SSLContextBuilder.create()
597                 .loadTrustMaterial(resource2, storePassword.toCharArray())
598                 .loadKeyMaterial(resource2, storePassword.toCharArray(), storePassword.toCharArray(), privateKeyStrategy)
599                 .build();
600         Assertions.assertNotNull(clientSslContext);
601         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
602         serverSocket.setNeedClientAuth(true);
603         serverSocket.bind(new InetSocketAddress(0));
604 
605         this.executorService = Executors.newSingleThreadExecutor();
606         final Future<Principal> future = this.executorService.submit(() -> {
607             try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
608                 final SSLSession session = socket.getSession();
609                 final Principal clientPrincipal = session.getPeerPrincipal();
610                 final OutputStream outputStream = socket.getOutputStream();
611                 outputStream.write(new byte[]{'H', 'i'});
612                 outputStream.flush();
613                 return clientPrincipal;
614             }
615         });
616         final int localPort = serverSocket.getLocalPort();
617         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
618             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
619             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
620             clientSocket.startHandshake();
621             final InputStream inputStream = clientSocket.getInputStream();
622             Assertions.assertEquals('H', inputStream.read());
623             Assertions.assertEquals('i', inputStream.read());
624             Assertions.assertEquals(-1, inputStream.read());
625         }
626 
627         final Principal clientPrincipal = future.get(5, TimeUnit.SECONDS);
628         Assertions.assertNotNull(clientPrincipal);
629         Assertions.assertEquals("CN=Test Client 2,OU=HttpComponents Project,O=Apache Software Foundation", clientPrincipal.getName());
630     }
631 
632 
633     @Test
634     public void testSSLHandshakeProtocolMismatch1() throws Exception {
635         final URL resource1 = getResource("/test-server.p12");
636         final String storePassword = "nopassword";
637         final String keyPassword = "nopassword";
638         final SSLContext serverSslContext = SSLContextBuilder.create()
639                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
640                 .build();
641         Assertions.assertNotNull(serverSslContext);
642         final URL resource2 = getResource("/test-client.p12");
643         final SSLContext clientSslContext = SSLContextBuilder.create()
644                 .loadTrustMaterial(resource2, storePassword.toCharArray())
645                 .build();
646         Assertions.assertNotNull(clientSslContext);
647         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
648         final Set<String> supportedServerProtocols = new LinkedHashSet<>(Arrays.asList(serverSocket.getSupportedProtocols()));
649         Assertions.assertTrue(supportedServerProtocols.contains("TLSv1"));
650         serverSocket.setEnabledProtocols(new String[] {"TLSv1"});
651         serverSocket.bind(new InetSocketAddress(0));
652 
653         this.executorService = Executors.newSingleThreadExecutor();
654         this.executorService.submit(() -> {
655             try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
656                 socket.getSession();
657             }
658             return Boolean.FALSE;
659         });
660 
661         final int localPort = serverSocket.getLocalPort();
662         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
663             final Set<String> supportedClientProtocols = new LinkedHashSet<>(Arrays.asList(clientSocket.getSupportedProtocols()));
664             Assertions.assertTrue(supportedClientProtocols.contains("SSLv3"));
665             clientSocket.setEnabledProtocols(new String[] {"SSLv3"} );
666             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
667             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
668             if (isWindows()) {
669                 Assertions.assertThrows(IOException.class, clientSocket::startHandshake);
670             } else {
671                 Assertions.assertThrows(SSLException.class, clientSocket::startHandshake);
672             }
673         }
674     }
675 
676     @Test
677     public void testSSLHandshakeProtocolMismatch2() throws Exception {
678         final URL resource1 = getResource("/test-server.p12");
679         final String storePassword = "nopassword";
680         final String keyPassword = "nopassword";
681         final SSLContext serverSslContext = SSLContextBuilder.create()
682                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
683                 .build();
684         Assertions.assertNotNull(serverSslContext);
685         final URL resource2 = getResource("/test-client.p12");
686         final SSLContext clientSslContext = SSLContextBuilder.create()
687                 .loadTrustMaterial(resource2, storePassword.toCharArray())
688                 .build();
689         Assertions.assertNotNull(clientSslContext);
690         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
691         final Set<String> supportedServerProtocols = new LinkedHashSet<>(Arrays.asList(serverSocket.getSupportedProtocols()));
692         Assertions.assertTrue(supportedServerProtocols.contains("SSLv3"));
693         serverSocket.setEnabledProtocols(new String[] {"SSLv3"});
694         serverSocket.bind(new InetSocketAddress(0));
695 
696         this.executorService = Executors.newSingleThreadExecutor();
697         this.executorService.submit(() -> {
698             try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
699                 socket.getSession();
700             }
701             return Boolean.FALSE;
702         });
703 
704         final int localPort = serverSocket.getLocalPort();
705         try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) {
706             final Set<String> supportedClientProtocols = new LinkedHashSet<>(
707                     Arrays.asList(clientSocket.getSupportedProtocols()));
708             Assertions.assertTrue(supportedClientProtocols.contains("TLSv1"));
709             clientSocket.setEnabledProtocols(new String[]{"TLSv1"});
710             clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound());
711             clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound());
712             if (isWindows()) {
713                 Assertions.assertThrows(IOException.class, clientSocket::startHandshake);
714             } else {
715                 Assertions.assertThrows(SSLException.class, clientSocket::startHandshake);
716             }
717         }
718     }
719 
720     @Test
721     public void testJSSEEndpointIdentification() throws Exception {
722         final URL resource1 = getResource("/test-server.p12");
723         final String storePassword = "nopassword";
724         final String keyPassword = "nopassword";
725         final SSLContext serverSslContext = SSLContextBuilder.create()
726                 .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
727                 .build();
728         Assertions.assertNotNull(serverSslContext);
729         final URL resource2 = getResource("/test-client.p12");
730         final SSLContext clientSslContext = SSLContextBuilder.create()
731                 .loadTrustMaterial(resource2, storePassword.toCharArray())
732                 .build();
733         Assertions.assertNotNull(clientSslContext);
734         final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket();
735         serverSocket.bind(new InetSocketAddress(0));
736 
737         this.executorService = Executors.newSingleThreadExecutor();
738         this.executorService.submit(() -> {
739             for (;;) {
740                 try (SSLSocket socket = (SSLSocket) serverSocket.accept()) {
741                     socket.getSession();
742                     socket.shutdownOutput();
743                 } catch (final IOException ex) {
744                     return Boolean.FALSE;
745                 }
746             }
747         });
748 
749         final int localPort1 = serverSocket.getLocalPort();
750         try (final Socket clientSocket = new Socket()) {
751             clientSocket.connect(new InetSocketAddress("localhost", localPort1));
752             try (SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket(clientSocket, "localhost", -1, true)) {
753                 final SSLParameters sslParameters = sslSocket.getSSLParameters();
754                 sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
755                 sslSocket.setSSLParameters(sslParameters);
756                 sslSocket.startHandshake();
757             }
758         }
759         final int localPort2 = serverSocket.getLocalPort();
760         try (final Socket clientSocket = new Socket()) {
761             clientSocket.connect(new InetSocketAddress("localhost", localPort2));
762             try (SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket(clientSocket, "otherhost", -1, true)) {
763                 final SSLParameters sslParameters = sslSocket.getSSLParameters();
764                 sslParameters.setEndpointIdentificationAlgorithm(null);
765                 sslSocket.setSSLParameters(sslParameters);
766                 sslSocket.startHandshake();
767             }
768         }
769         final int localPort3 = serverSocket.getLocalPort();
770 
771         Assertions.assertThrows(SSLException.class, () -> {
772             try (final Socket clientSocket = new Socket()) {
773                 clientSocket.connect(new InetSocketAddress("localhost", localPort3));
774                 try (SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket(clientSocket, "otherhost", -1, true)) {
775                     final SSLParameters sslParameters = sslSocket.getSSLParameters();
776                     sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
777                     sslSocket.setSSLParameters(sslParameters);
778                     sslSocket.startHandshake();
779                 }
780             }
781         });
782     }
783 
784 }