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.util.Arrays;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.regex.Pattern;
43
44 import javax.net.ssl.HostnameVerifier;
45 import javax.net.ssl.SSLContext;
46 import javax.net.ssl.SSLException;
47 import javax.net.ssl.SSLHandshakeException;
48 import javax.net.ssl.SSLSession;
49 import javax.net.ssl.SSLSocket;
50
51 import org.apache.hc.client5.http.config.TlsConfig;
52 import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
53 import org.apache.hc.core5.annotation.Contract;
54 import org.apache.hc.core5.annotation.ThreadingBehavior;
55 import org.apache.hc.core5.http.HttpHost;
56 import org.apache.hc.core5.http.protocol.HttpContext;
57 import org.apache.hc.core5.http.ssl.TLS;
58 import org.apache.hc.core5.http.ssl.TlsCiphers;
59 import org.apache.hc.core5.io.Closer;
60 import org.apache.hc.core5.ssl.SSLContexts;
61 import org.apache.hc.core5.ssl.SSLInitializationException;
62 import org.apache.hc.core5.util.Args;
63 import org.apache.hc.core5.util.Asserts;
64 import org.apache.hc.core5.util.TimeValue;
65 import org.apache.hc.core5.util.Timeout;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69
70
71
72
73
74
75
76
77 @Contract(threading = ThreadingBehavior.STATELESS)
78 public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactory {
79
80 private static final String WEAK_KEY_EXCHANGES
81 = "^(TLS|SSL)_(NULL|ECDH_anon|DH_anon|DH_anon_EXPORT|DHE_RSA_EXPORT|DHE_DSS_EXPORT|"
82 + "DSS_EXPORT|DH_DSS_EXPORT|DH_RSA_EXPORT|RSA_EXPORT|KRB5_EXPORT)_(.*)";
83 private static final String WEAK_CIPHERS
84 = "^(TLS|SSL)_(.*)_WITH_(NULL|DES_CBC|DES40_CBC|DES_CBC_40|3DES_EDE_CBC|RC4_128|RC4_40|RC2_CBC_40)_(.*)";
85 private static final List<Pattern> WEAK_CIPHER_SUITE_PATTERNS = Collections.unmodifiableList(Arrays.asList(
86 Pattern.compile(WEAK_KEY_EXCHANGES, Pattern.CASE_INSENSITIVE),
87 Pattern.compile(WEAK_CIPHERS, Pattern.CASE_INSENSITIVE)));
88
89 private static final Logger LOG = LoggerFactory.getLogger(SSLConnectionSocketFactory.class);
90
91
92
93
94
95
96
97
98 public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException {
99 return new SSLConnectionSocketFactory(SSLContexts.createDefault(), HttpsSupport.getDefaultHostnameVerifier());
100 }
101
102
103
104
105
106
107
108
109
110 public static SSLConnectionSocketFactory getSystemSocketFactory() throws SSLInitializationException {
111 return new SSLConnectionSocketFactory(
112 (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
113 HttpsSupport.getSystemProtocols(),
114 HttpsSupport.getSystemCipherSuits(),
115 HttpsSupport.getDefaultHostnameVerifier());
116 }
117
118 static boolean isWeakCipherSuite(final String cipherSuite) {
119 for (final Pattern pattern : WEAK_CIPHER_SUITE_PATTERNS) {
120 if (pattern.matcher(cipherSuite).matches()) {
121 return true;
122 }
123 }
124 return false;
125 }
126
127 private final javax.net.ssl.SSLSocketFactory socketFactory;
128 private final HostnameVerifier hostnameVerifier;
129 private final String[] supportedProtocols;
130 private final String[] supportedCipherSuites;
131 private final TlsSessionValidator tlsSessionValidator;
132
133 public SSLConnectionSocketFactory(final SSLContext sslContext) {
134 this(sslContext, HttpsSupport.getDefaultHostnameVerifier());
135 }
136
137
138
139
140 public SSLConnectionSocketFactory(
141 final SSLContext sslContext, final HostnameVerifier hostnameVerifier) {
142 this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
143 null, null, hostnameVerifier);
144 }
145
146
147
148
149 public SSLConnectionSocketFactory(
150 final SSLContext sslContext,
151 final String[] supportedProtocols,
152 final String[] supportedCipherSuites,
153 final HostnameVerifier hostnameVerifier) {
154 this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
155 supportedProtocols, supportedCipherSuites, hostnameVerifier);
156 }
157
158
159
160
161 public SSLConnectionSocketFactory(
162 final javax.net.ssl.SSLSocketFactory socketFactory,
163 final HostnameVerifier hostnameVerifier) {
164 this(socketFactory, null, null, hostnameVerifier);
165 }
166
167
168
169
170 public SSLConnectionSocketFactory(
171 final javax.net.ssl.SSLSocketFactory socketFactory,
172 final String[] supportedProtocols,
173 final String[] supportedCipherSuites,
174 final HostnameVerifier hostnameVerifier) {
175 this.socketFactory = Args.notNull(socketFactory, "SSL socket factory");
176 this.supportedProtocols = supportedProtocols;
177 this.supportedCipherSuites = supportedCipherSuites;
178 this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : HttpsSupport.getDefaultHostnameVerifier();
179 this.tlsSessionValidator = new TlsSessionValidator(LOG);
180 }
181
182
183
184
185 @Deprecated
186 protected void prepareSocket(final SSLSocket socket) throws IOException {
187 }
188
189
190
191
192
193
194
195
196
197 @SuppressWarnings("deprecation")
198 protected void prepareSocket(final SSLSocket socket, final HttpContext context) throws IOException {
199 prepareSocket(socket);
200 }
201
202 @Override
203 public Socket createSocket(final HttpContext context) throws IOException {
204 return new Socket();
205 }
206
207 @Override
208 public Socket createSocket(final Proxy proxy, final HttpContext context) throws IOException {
209 return proxy != null ? new Socket(proxy) : createSocket(context);
210 }
211
212 @Override
213 public Socket connectSocket(
214 final TimeValue connectTimeout,
215 final Socket socket,
216 final HttpHost host,
217 final InetSocketAddress remoteAddress,
218 final InetSocketAddress localAddress,
219 final HttpContext context) throws IOException {
220 final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null;
221 return connectSocket(socket, host, remoteAddress, localAddress, timeout, timeout, context);
222 }
223
224 @Override
225 public Socket connectSocket(
226 final Socket socket,
227 final HttpHost host,
228 final InetSocketAddress remoteAddress,
229 final InetSocketAddress localAddress,
230 final Timeout connectTimeout,
231 final Object attachment,
232 final HttpContext context) throws IOException {
233 Args.notNull(host, "HTTP host");
234 Args.notNull(remoteAddress, "Remote address");
235 final Socket sock = socket != null ? socket : createSocket(context);
236 if (localAddress != null) {
237 sock.bind(localAddress);
238 }
239 try {
240 connectSocket(sock, remoteAddress, connectTimeout, context);
241 } catch (final IOException ex) {
242 Closer.closeQuietly(sock);
243 throw ex;
244 }
245
246 if (sock instanceof SSLSocket) {
247 final SSLSocket sslsock = (SSLSocket) sock;
248 executeHandshake(sslsock, host.getHostName(), attachment, context);
249 return sock;
250 }
251 return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), attachment, context);
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 protected void connectSocket(
268 final Socket sock,
269 final InetSocketAddress remoteAddress,
270 final Timeout connectTimeout,
271 final HttpContext context) throws IOException {
272 Args.notNull(sock, "Socket");
273 Args.notNull(remoteAddress, "Remote address");
274 if (LOG.isDebugEnabled()) {
275 LOG.debug("Connecting socket to {} with timeout {}", remoteAddress, connectTimeout);
276 }
277
278
279 try {
280 AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
281 sock.connect(remoteAddress, Timeout.defaultsToDisabled(connectTimeout).toMillisecondsIntBound());
282 return null;
283 });
284 } catch (final PrivilegedActionException e) {
285 Asserts.check(e.getCause() instanceof IOException,
286 "method contract violation only checked exceptions are wrapped: " + e.getCause());
287
288 throw (IOException) e.getCause();
289 }
290 }
291
292 @Override
293 public Socket createLayeredSocket(
294 final Socket socket,
295 final String target,
296 final int port,
297 final HttpContext context) throws IOException {
298 return createLayeredSocket(socket, target, port, null, context);
299 }
300
301 @Override
302 public Socket createLayeredSocket(
303 final Socket socket,
304 final String target,
305 final int port,
306 final Object attachment,
307 final HttpContext context) throws IOException {
308 final SSLSocket sslsock = (SSLSocket) this.socketFactory.createSocket(
309 socket,
310 target,
311 port,
312 true);
313 executeHandshake(sslsock, target, attachment, context);
314 return sslsock;
315 }
316
317 private void executeHandshake(
318 final SSLSocket sslsock,
319 final String target,
320 final Object attachment,
321 final HttpContext context) throws IOException {
322 final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
323 if (supportedProtocols != null) {
324 sslsock.setEnabledProtocols(supportedProtocols);
325 } else {
326 sslsock.setEnabledProtocols((TLS.excludeWeak(sslsock.getEnabledProtocols())));
327 }
328 if (supportedCipherSuites != null) {
329 sslsock.setEnabledCipherSuites(supportedCipherSuites);
330 } else {
331 sslsock.setEnabledCipherSuites(TlsCiphers.excludeWeak(sslsock.getEnabledCipherSuites()));
332 }
333 final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
334 if (handshakeTimeout != null) {
335 sslsock.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
336 }
337
338 prepareSocket(sslsock, context);
339
340 if (LOG.isDebugEnabled()) {
341 LOG.debug("Enabled protocols: {}", (Object) sslsock.getEnabledProtocols());
342 LOG.debug("Enabled cipher suites: {}", (Object) sslsock.getEnabledCipherSuites());
343 LOG.debug("Starting handshake ({})", handshakeTimeout);
344 }
345 sslsock.startHandshake();
346 verifyHostname(sslsock, target);
347 }
348
349 private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
350 try {
351 SSLSession session = sslsock.getSession();
352 if (session == null) {
353
354
355
356 final InputStream in = sslsock.getInputStream();
357 in.available();
358
359
360 session = sslsock.getSession();
361 if (session == null) {
362
363
364 sslsock.startHandshake();
365 session = sslsock.getSession();
366 }
367 }
368 if (session == null) {
369 throw new SSLHandshakeException("SSL session not available");
370 }
371 verifySession(hostname, session);
372 } catch (final IOException iox) {
373
374 Closer.closeQuietly(sslsock);
375 throw iox;
376 }
377 }
378
379 protected void verifySession(
380 final String hostname,
381 final SSLSession sslSession) throws SSLException {
382 tlsSessionValidator.verifySession(hostname, sslSession, hostnameVerifier);
383 }
384
385 }