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.Socket;
34 import java.security.AccessController;
35 import java.security.PrivilegedActionException;
36 import java.security.PrivilegedExceptionAction;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.regex.Pattern;
41
42 import javax.net.SocketFactory;
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
185
186
187
188
189 protected void prepareSocket(final SSLSocket socket) throws IOException {
190 }
191
192 @Override
193 public Socket createSocket(final HttpContext context) throws IOException {
194 return SocketFactory.getDefault().createSocket();
195 }
196
197 @Override
198 public Socket connectSocket(
199 final TimeValue connectTimeout,
200 final Socket socket,
201 final HttpHost host,
202 final InetSocketAddress remoteAddress,
203 final InetSocketAddress localAddress,
204 final HttpContext context) throws IOException {
205 final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null;
206 return connectSocket(socket, host, remoteAddress, localAddress, timeout, timeout, context);
207 }
208
209 @Override
210 public Socket connectSocket(
211 final Socket socket,
212 final HttpHost host,
213 final InetSocketAddress remoteAddress,
214 final InetSocketAddress localAddress,
215 final Timeout connectTimeout,
216 final Object attachment,
217 final HttpContext context) throws IOException {
218 Args.notNull(host, "HTTP host");
219 Args.notNull(remoteAddress, "Remote address");
220 final Socket sock = socket != null ? socket : createSocket(context);
221 if (localAddress != null) {
222 sock.bind(localAddress);
223 }
224 try {
225 if (LOG.isDebugEnabled()) {
226 LOG.debug("Connecting socket to {} with timeout {}", remoteAddress, connectTimeout);
227 }
228
229
230 try {
231 AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
232 sock.connect(remoteAddress, Timeout.defaultsToDisabled(connectTimeout).toMillisecondsIntBound());
233 return null;
234 });
235 } catch (final PrivilegedActionException e) {
236 Asserts.check(e.getCause() instanceof IOException,
237 "method contract violation only checked exceptions are wrapped: " + e.getCause());
238
239 throw (IOException) e.getCause();
240 }
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);
249 return sock;
250 }
251 return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), attachment, context);
252 }
253
254 @Override
255 public Socket createLayeredSocket(
256 final Socket socket,
257 final String target,
258 final int port,
259 final HttpContext context) throws IOException {
260 return createLayeredSocket(socket, target, port, null, context);
261 }
262
263 @Override
264 public Socket createLayeredSocket(
265 final Socket socket,
266 final String target,
267 final int port,
268 final Object attachment,
269 final HttpContext context) throws IOException {
270 final SSLSocket sslsock = (SSLSocket) this.socketFactory.createSocket(
271 socket,
272 target,
273 port,
274 true);
275 executeHandshake(sslsock, target, attachment);
276 return sslsock;
277 }
278
279 private void executeHandshake(final SSLSocket sslsock, final String target, final Object attachment) throws IOException {
280 final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
281 if (supportedProtocols != null) {
282 sslsock.setEnabledProtocols(supportedProtocols);
283 } else {
284 sslsock.setEnabledProtocols((TLS.excludeWeak(sslsock.getEnabledProtocols())));
285 }
286 if (supportedCipherSuites != null) {
287 sslsock.setEnabledCipherSuites(supportedCipherSuites);
288 } else {
289 sslsock.setEnabledCipherSuites(TlsCiphers.excludeWeak(sslsock.getEnabledCipherSuites()));
290 }
291 final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
292 if (handshakeTimeout != null) {
293 sslsock.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
294 }
295
296 prepareSocket(sslsock);
297
298 if (LOG.isDebugEnabled()) {
299 LOG.debug("Enabled protocols: {}", (Object) sslsock.getEnabledProtocols());
300 LOG.debug("Enabled cipher suites: {}", (Object) sslsock.getEnabledCipherSuites());
301 LOG.debug("Starting handshake ({})", handshakeTimeout);
302 }
303 sslsock.startHandshake();
304 verifyHostname(sslsock, target);
305 }
306
307 private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
308 try {
309 SSLSession session = sslsock.getSession();
310 if (session == null) {
311
312
313
314 final InputStream in = sslsock.getInputStream();
315 in.available();
316
317
318 session = sslsock.getSession();
319 if (session == null) {
320
321
322 sslsock.startHandshake();
323 session = sslsock.getSession();
324 }
325 }
326 if (session == null) {
327 throw new SSLHandshakeException("SSL session not available");
328 }
329 verifySession(hostname, session);
330 } catch (final IOException iox) {
331
332 Closer.closeQuietly(sslsock);
333 throw iox;
334 }
335 }
336
337 protected void verifySession(
338 final String hostname,
339 final SSLSession sslSession) throws SSLException {
340 tlsSessionValidator.verifySession(hostname, sslSession, hostnameVerifier);
341 }
342
343 }