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