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