1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.ssl;
29
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.InetSocketAddress;
33 import java.net.Proxy;
34 import java.net.Socket;
35 import java.net.SocketAddress;
36 import java.security.AccessController;
37 import java.security.PrivilegedActionException;
38 import java.security.PrivilegedExceptionAction;
39 import java.security.cert.Certificate;
40 import java.security.cert.X509Certificate;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.regex.Pattern;
48
49 import javax.net.ssl.HostnameVerifier;
50 import javax.net.ssl.SSLContext;
51 import javax.net.ssl.SSLException;
52 import javax.net.ssl.SSLHandshakeException;
53 import javax.net.ssl.SSLPeerUnverifiedException;
54 import javax.net.ssl.SSLSession;
55 import javax.net.ssl.SSLSocket;
56 import javax.security.auth.x500.X500Principal;
57
58 import org.apache.hc.client5.http.config.TlsConfig;
59 import org.apache.hc.core5.annotation.Contract;
60 import org.apache.hc.core5.annotation.ThreadingBehavior;
61 import org.apache.hc.core5.http.HttpHost;
62 import org.apache.hc.core5.http.protocol.HttpContext;
63 import org.apache.hc.core5.http.ssl.TLS;
64 import org.apache.hc.core5.http.ssl.TlsCiphers;
65 import org.apache.hc.core5.io.Closer;
66 import org.apache.hc.core5.ssl.SSLContexts;
67 import org.apache.hc.core5.ssl.SSLInitializationException;
68 import org.apache.hc.core5.util.Args;
69 import org.apache.hc.core5.util.Asserts;
70 import org.apache.hc.core5.util.TimeValue;
71 import org.apache.hc.core5.util.Timeout;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75
76
77
78
79
80
81
82
83 @Deprecated
84 @Contract(threading = ThreadingBehavior.STATELESS)
85 public class SSLConnectionSocketFactory implements org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory {
86
87 private static final String WEAK_KEY_EXCHANGES
88 = "^(TLS|SSL)_(NULL|ECDH_anon|DH_anon|DH_anon_EXPORT|DHE_RSA_EXPORT|DHE_DSS_EXPORT|"
89 + "DSS_EXPORT|DH_DSS_EXPORT|DH_RSA_EXPORT|RSA_EXPORT|KRB5_EXPORT)_(.*)";
90 private static final String WEAK_CIPHERS
91 = "^(TLS|SSL)_(.*)_WITH_(NULL|DES_CBC|DES40_CBC|DES_CBC_40|3DES_EDE_CBC|RC4_128|RC4_40|RC2_CBC_40)_(.*)";
92 private static final List<Pattern> WEAK_CIPHER_SUITE_PATTERNS = Collections.unmodifiableList(Arrays.asList(
93 Pattern.compile(WEAK_KEY_EXCHANGES, Pattern.CASE_INSENSITIVE),
94 Pattern.compile(WEAK_CIPHERS, Pattern.CASE_INSENSITIVE)));
95
96 private static final Logger LOG = LoggerFactory.getLogger(SSLConnectionSocketFactory.class);
97
98
99
100
101
102
103
104
105 public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException {
106 return new SSLConnectionSocketFactory(SSLContexts.createDefault(), HttpsSupport.getDefaultHostnameVerifier());
107 }
108
109
110
111
112
113
114
115
116
117 public static SSLConnectionSocketFactory getSystemSocketFactory() throws SSLInitializationException {
118 return new SSLConnectionSocketFactory(
119 (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
120 HttpsSupport.getSystemProtocols(),
121 HttpsSupport.getSystemCipherSuits(),
122 HttpsSupport.getDefaultHostnameVerifier());
123 }
124
125 static boolean isWeakCipherSuite(final String cipherSuite) {
126 for (final Pattern pattern : WEAK_CIPHER_SUITE_PATTERNS) {
127 if (pattern.matcher(cipherSuite).matches()) {
128 return true;
129 }
130 }
131 return false;
132 }
133
134 private final javax.net.ssl.SSLSocketFactory socketFactory;
135 private final HostnameVerifier hostnameVerifier;
136 private final String[] supportedProtocols;
137 private final String[] supportedCipherSuites;
138
139 public SSLConnectionSocketFactory(final SSLContext sslContext) {
140 this(sslContext, HttpsSupport.getDefaultHostnameVerifier());
141 }
142
143
144
145
146 public SSLConnectionSocketFactory(
147 final SSLContext sslContext, final HostnameVerifier hostnameVerifier) {
148 this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
149 null, null, hostnameVerifier);
150 }
151
152
153
154
155 public SSLConnectionSocketFactory(
156 final SSLContext sslContext,
157 final String[] supportedProtocols,
158 final String[] supportedCipherSuites,
159 final HostnameVerifier hostnameVerifier) {
160 this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
161 supportedProtocols, supportedCipherSuites, hostnameVerifier);
162 }
163
164
165
166
167 public SSLConnectionSocketFactory(
168 final javax.net.ssl.SSLSocketFactory socketFactory,
169 final HostnameVerifier hostnameVerifier) {
170 this(socketFactory, null, null, hostnameVerifier);
171 }
172
173
174
175
176 public SSLConnectionSocketFactory(
177 final javax.net.ssl.SSLSocketFactory socketFactory,
178 final String[] supportedProtocols,
179 final String[] supportedCipherSuites,
180 final HostnameVerifier hostnameVerifier) {
181 this.socketFactory = Args.notNull(socketFactory, "SSL socket factory");
182 this.supportedProtocols = supportedProtocols;
183 this.supportedCipherSuites = supportedCipherSuites;
184 this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : HttpsSupport.getDefaultHostnameVerifier();
185 }
186
187
188
189
190 @Deprecated
191 protected void prepareSocket(final SSLSocket socket) throws IOException {
192 }
193
194
195
196
197
198
199
200
201
202 @SuppressWarnings("deprecation")
203 protected void prepareSocket(final SSLSocket socket, final HttpContext context) throws IOException {
204 prepareSocket(socket);
205 }
206
207 @Override
208 public Socket createSocket(final HttpContext context) throws IOException {
209 return new Socket();
210 }
211
212 @Override
213 public Socket createSocket(final Proxy proxy, final HttpContext context) throws IOException {
214 return proxy != null ? new Socket(proxy) : createSocket(context);
215 }
216
217 @Override
218 public Socket connectSocket(
219 final TimeValue connectTimeout,
220 final Socket socket,
221 final HttpHost host,
222 final InetSocketAddress remoteAddress,
223 final InetSocketAddress localAddress,
224 final HttpContext context) throws IOException {
225 final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null;
226 return connectSocket(socket, host, remoteAddress, localAddress, timeout, timeout, context);
227 }
228
229 @Override
230 public Socket connectSocket(
231 final Socket socket,
232 final HttpHost host,
233 final InetSocketAddress remoteAddress,
234 final InetSocketAddress localAddress,
235 final Timeout connectTimeout,
236 final Object attachment,
237 final HttpContext context) throws IOException {
238 Args.notNull(host, "HTTP host");
239 Args.notNull(remoteAddress, "Remote address");
240 final Socket sock = socket != null ? socket : createSocket(context);
241 if (localAddress != null) {
242 sock.bind(localAddress);
243 }
244 try {
245 connectSocket(sock, remoteAddress, connectTimeout, context);
246 } catch (final IOException ex) {
247 Closer.closeQuietly(sock);
248 throw ex;
249 }
250
251 if (sock instanceof SSLSocket) {
252 final SSLSocket sslsock = (SSLSocket) sock;
253 executeHandshake(sslsock, host.getHostName(), attachment, context);
254 return sock;
255 }
256 return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), attachment, context);
257 }
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272 protected void connectSocket(
273 final Socket sock,
274 final InetSocketAddress remoteAddress,
275 final Timeout connectTimeout,
276 final HttpContext context) throws IOException {
277 Args.notNull(sock, "Socket");
278 Args.notNull(remoteAddress, "Remote address");
279 if (LOG.isDebugEnabled()) {
280 LOG.debug("Connecting socket to {} with timeout {}", remoteAddress, connectTimeout);
281 }
282
283
284 try {
285 AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
286 sock.connect(remoteAddress, Timeout.defaultsToDisabled(connectTimeout).toMillisecondsIntBound());
287 return null;
288 });
289 } catch (final PrivilegedActionException e) {
290 Asserts.check(e.getCause() instanceof IOException,
291 "method contract violation only checked exceptions are wrapped: " + e.getCause());
292
293 throw (IOException) e.getCause();
294 }
295 }
296
297 @Override
298 public Socket createLayeredSocket(
299 final Socket socket,
300 final String target,
301 final int port,
302 final HttpContext context) throws IOException {
303 return createLayeredSocket(socket, target, port, null, context);
304 }
305
306 @Override
307 public Socket createLayeredSocket(
308 final Socket socket,
309 final String target,
310 final int port,
311 final Object attachment,
312 final HttpContext context) throws IOException {
313 final SSLSocket sslsock = (SSLSocket) this.socketFactory.createSocket(
314 socket,
315 target,
316 port,
317 true);
318 executeHandshake(sslsock, target, attachment, context);
319 return sslsock;
320 }
321
322 private void executeHandshake(
323 final SSLSocket sslsock,
324 final String target,
325 final Object attachment,
326 final HttpContext context) throws IOException {
327 final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
328 if (supportedProtocols != null) {
329 sslsock.setEnabledProtocols(supportedProtocols);
330 } else {
331 sslsock.setEnabledProtocols((TLS.excludeWeak(sslsock.getEnabledProtocols())));
332 }
333 if (supportedCipherSuites != null) {
334 sslsock.setEnabledCipherSuites(supportedCipherSuites);
335 } else {
336 sslsock.setEnabledCipherSuites(TlsCiphers.excludeWeak(sslsock.getEnabledCipherSuites()));
337 }
338 final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
339 if (handshakeTimeout != null) {
340 sslsock.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
341 }
342
343 prepareSocket(sslsock, context);
344
345 if (LOG.isDebugEnabled()) {
346 LOG.debug("Enabled protocols: {}", (Object) sslsock.getEnabledProtocols());
347 LOG.debug("Enabled cipher suites: {}", (Object) sslsock.getEnabledCipherSuites());
348 LOG.debug("Starting handshake ({})", handshakeTimeout);
349 }
350 sslsock.startHandshake();
351 verifyHostname(sslsock, target);
352 }
353
354 private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
355 try {
356 SSLSession session = sslsock.getSession();
357 if (session == null) {
358
359
360
361 final InputStream in = sslsock.getInputStream();
362 in.available();
363
364
365 session = sslsock.getSession();
366 if (session == null) {
367
368
369 sslsock.startHandshake();
370 session = sslsock.getSession();
371 }
372 }
373 if (session == null) {
374 throw new SSLHandshakeException("SSL session not available");
375 }
376 verifySession(hostname, session);
377 } catch (final IOException iox) {
378
379 Closer.closeQuietly(sslsock);
380 throw iox;
381 }
382 }
383
384 protected void verifySession(
385 final String hostname,
386 final SSLSession sslSession) throws SSLException {
387 verifySession(hostname, sslSession, hostnameVerifier);
388 }
389
390 void verifySession(
391 final String hostname,
392 final SSLSession sslsession,
393 final HostnameVerifier hostnameVerifier) throws SSLException {
394
395 if (LOG.isDebugEnabled()) {
396 LOG.debug("Secure session established");
397 LOG.debug(" negotiated protocol: {}", sslsession.getProtocol());
398 LOG.debug(" negotiated cipher suite: {}", sslsession.getCipherSuite());
399
400 try {
401
402 final Certificate[] certs = sslsession.getPeerCertificates();
403 final Certificate cert = certs[0];
404 if (cert instanceof X509Certificate) {
405 final X509Certificate x509 = (X509Certificate) cert;
406 final X500Principal peer = x509.getSubjectX500Principal();
407
408 LOG.debug(" peer principal: {}", peer);
409 final Collection<List<?>> altNames1 = x509.getSubjectAlternativeNames();
410 if (altNames1 != null) {
411 final List<String> altNames = new ArrayList<>();
412 for (final List<?> aC : altNames1) {
413 if (!aC.isEmpty()) {
414 altNames.add(Objects.toString(aC.get(1), null));
415 }
416 }
417 LOG.debug(" peer alternative names: {}", altNames);
418 }
419
420 final X500Principal issuer = x509.getIssuerX500Principal();
421 LOG.debug(" issuer principal: {}", issuer);
422 final Collection<List<?>> altNames2 = x509.getIssuerAlternativeNames();
423 if (altNames2 != null) {
424 final List<String> altNames = new ArrayList<>();
425 for (final List<?> aC : altNames2) {
426 if (!aC.isEmpty()) {
427 altNames.add(Objects.toString(aC.get(1), null));
428 }
429 }
430 LOG.debug(" issuer alternative names: {}", altNames);
431 }
432 }
433 } catch (final Exception ignore) {
434 }
435 }
436
437 if (hostnameVerifier != null) {
438 final Certificate[] certs = sslsession.getPeerCertificates();
439 if (certs.length < 1) {
440 throw new SSLPeerUnverifiedException("Peer certificate chain is empty");
441 }
442 final Certificate peerCertificate = certs[0];
443 final X509Certificate x509Certificate;
444 if (peerCertificate instanceof X509Certificate) {
445 x509Certificate = (X509Certificate) peerCertificate;
446 } else {
447 throw new SSLPeerUnverifiedException("Unexpected certificate type: " + peerCertificate.getType());
448 }
449 if (hostnameVerifier instanceof HttpClientHostnameVerifier) {
450 ((HttpClientHostnameVerifier) hostnameVerifier).verify(hostname, x509Certificate);
451 } else if (!hostnameVerifier.verify(hostname, sslsession)) {
452 final List<SubjectName> subjectAlts = DefaultHostnameVerifier.getSubjectAltNames(x509Certificate);
453 throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
454 "of the subject alternative names: " + subjectAlts);
455 }
456 }
457 }
458
459 }