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