1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.httpclient;
16
17 import java.io.IOException;
18 import java.lang.reflect.Field;
19 import java.net.InetSocketAddress;
20 import java.net.Socket;
21 import java.net.SocketTimeoutException;
22 import java.security.GeneralSecurityException;
23 import java.security.KeyStore;
24 import java.security.cert.CertificateException;
25 import java.security.cert.X509Certificate;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.Set;
29
30 import javax.net.ssl.HostnameVerifier;
31 import javax.net.ssl.KeyManager;
32 import javax.net.ssl.KeyManagerFactory;
33 import javax.net.ssl.SSLContext;
34 import javax.net.ssl.SSLEngine;
35 import javax.net.ssl.SSLSocket;
36 import javax.net.ssl.X509ExtendedTrustManager;
37
38 import org.apache.http.HttpHost;
39 import org.apache.http.conn.ConnectTimeoutException;
40 import org.apache.http.conn.ssl.DefaultHostnameVerifier;
41 import org.apache.http.conn.ssl.NoopHostnameVerifier;
42 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
43 import org.apache.http.protocol.HttpContext;
44 import org.apache.http.ssl.SSLContexts;
45 import org.htmlunit.WebClientOptions;
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public final class HtmlUnitSSLConnectionSocketFactory extends SSLConnectionSocketFactory {
60 private static final String SSL3ONLY = "htmlunit.SSL3Only";
61
62 private final boolean useInsecureSSL_;
63
64
65
66
67
68
69 public static void setUseSSL3Only(final HttpContext httpContext, final boolean ssl3Only) {
70 httpContext.setAttribute(SSL3ONLY, ssl3Only);
71 }
72
73 static boolean isUseSSL3Only(final HttpContext context) {
74 return "TRUE".equalsIgnoreCase((String) context.getAttribute(SSL3ONLY));
75 }
76
77
78
79
80
81
82 public static SSLConnectionSocketFactory buildSSLSocketFactory(final WebClientOptions options) {
83 try {
84 final String[] sslClientProtocols = options.getSSLClientProtocols();
85 final String[] sslClientCipherSuites = options.getSSLClientCipherSuites();
86
87 SSLContext sslContext = options.getSSLContext();
88 final boolean useInsecureSSL = options.isUseInsecureSSL();
89
90 if (useInsecureSSL) {
91
92 String protocol = options.getSSLInsecureProtocol();
93 if (protocol == null) {
94 protocol = "SSL";
95 }
96 if (sslContext == null) {
97 sslContext = SSLContext.getInstance(protocol);
98 sslContext.init(getKeyManagers(options),
99 new X509ExtendedTrustManager[] {new InsecureTrustManager()}, null);
100 }
101
102 return new HtmlUnitSSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE,
103 true, sslClientProtocols, sslClientCipherSuites);
104 }
105
106 final KeyStore keyStore = options.getSSLClientCertificateStore();
107 final char[] keyStorePassword = keyStore == null ? null : options.getSSLClientCertificatePassword();
108 final KeyStore trustStore = options.getSSLTrustStore();
109
110 if (sslContext == null) {
111 sslContext = SSLContexts.custom()
112 .loadKeyMaterial(keyStore, keyStorePassword).loadTrustMaterial(trustStore, null).build();
113 }
114 return new HtmlUnitSSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier(),
115 false, sslClientProtocols, sslClientCipherSuites);
116 }
117 catch (final GeneralSecurityException e) {
118 throw new RuntimeException(e);
119 }
120 }
121
122 private HtmlUnitSSLConnectionSocketFactory(final SSLContext sslContext,
123 final HostnameVerifier hostnameVerifier, final boolean useInsecureSSL,
124 final String[] supportedProtocols, final String[] supportedCipherSuites) {
125 super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
126 useInsecureSSL_ = useInsecureSSL;
127 }
128
129 private static void configureSocket(final SSLSocket sslSocket, final HttpContext context) {
130 if (isUseSSL3Only(context)) {
131 sslSocket.setEnabledProtocols(new String[]{"SSLv3"});
132 }
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146 @Override
147 public Socket connectSocket(
148 final int connectTimeout,
149 final Socket socket,
150 final HttpHost host,
151 final InetSocketAddress remoteAddress,
152 final InetSocketAddress localAddress,
153 final HttpContext context) throws IOException {
154 final HttpHost socksProxy = SocksConnectionSocketFactory.getSocksProxy(context);
155 if (socksProxy != null) {
156 final Socket underlying = SocksConnectionSocketFactory.createSocketWithSocksProxy(socksProxy);
157 underlying.setReuseAddress(true);
158
159 try {
160
161 underlying.connect(remoteAddress, connectTimeout);
162 }
163 catch (final SocketTimeoutException ex) {
164 final ConnectTimeoutException cex =
165 new ConnectTimeoutException("Connect to " + socksProxy.toURI() + " timed out");
166 cex.initCause(ex);
167 throw cex;
168 }
169
170 final Socket sslSocket = getSSLSocketFactory().createSocket(underlying, remoteAddress.getHostName(),
171 remoteAddress.getPort(), true);
172 configureSocket((SSLSocket) sslSocket, context);
173 return sslSocket;
174 }
175 try {
176 return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
177 }
178 catch (final IOException e) {
179 if (useInsecureSSL_ && "handshake alert: unrecognized_name".equals(e.getMessage())) {
180 setEmptyHostname(host);
181
182 return super.connectSocket(connectTimeout,
183 createSocket(context),
184 host, remoteAddress, localAddress, context);
185 }
186 throw e;
187 }
188 }
189
190 private static void setEmptyHostname(final HttpHost host) {
191 try {
192 final Field field = HttpHost.class.getDeclaredField("hostname");
193 field.setAccessible(true);
194 field.set(host, "");
195 }
196 catch (final Exception e) {
197 throw new RuntimeException(e);
198 }
199 }
200
201 private javax.net.ssl.SSLSocketFactory getSSLSocketFactory() {
202 try {
203 final Field field = SSLConnectionSocketFactory.class.getDeclaredField("socketfactory");
204 field.setAccessible(true);
205 return (javax.net.ssl.SSLSocketFactory) field.get(this);
206 }
207 catch (final Exception e) {
208 throw new RuntimeException(e);
209 }
210 }
211
212 private static KeyManager[] getKeyManagers(final WebClientOptions options) {
213 if (options.getSSLClientCertificateStore() == null) {
214 return null;
215 }
216
217 try {
218 final KeyStore keyStore = options.getSSLClientCertificateStore();
219 final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
220 KeyManagerFactory.getDefaultAlgorithm());
221 keyManagerFactory.init(keyStore, options.getSSLClientCertificatePassword());
222 return keyManagerFactory.getKeyManagers();
223 }
224 catch (final Exception e) {
225 throw new RuntimeException(e);
226 }
227 }
228 }
229
230
231
232
233
234
235
236
237
238 class InsecureTrustManager extends X509ExtendedTrustManager {
239 private final Set<X509Certificate> acceptedIssuers_ = new HashSet<>();
240
241
242
243
244 @Override
245 public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
246
247 acceptedIssuers_.addAll(Arrays.asList(chain));
248 }
249
250
251
252
253 @Override
254 public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
255
256 acceptedIssuers_.addAll(Arrays.asList(chain));
257 }
258
259 @Override
260 public void checkClientTrusted(final X509Certificate[] chain,
261 final String authType, final Socket socket) throws CertificateException {
262
263 acceptedIssuers_.addAll(Arrays.asList(chain));
264 }
265
266 @Override
267 public void checkClientTrusted(final X509Certificate[] chain,
268 final String authType, final SSLEngine sslEngine) throws CertificateException {
269
270 acceptedIssuers_.addAll(Arrays.asList(chain));
271 }
272
273 @Override
274 public void checkServerTrusted(final X509Certificate[] chain,
275 final String authType, final Socket socket) throws CertificateException {
276
277 acceptedIssuers_.addAll(Arrays.asList(chain));
278 }
279
280 @Override
281 public void checkServerTrusted(final X509Certificate[] chain,
282 final String authType, final SSLEngine sslEngine) throws CertificateException {
283
284 acceptedIssuers_.addAll(Arrays.asList(chain));
285 }
286
287
288
289
290 @Override
291 public X509Certificate[] getAcceptedIssuers() {
292
293
294
295
296 if (acceptedIssuers_.isEmpty()) {
297 return new X509Certificate[0];
298 }
299 return acceptedIssuers_.toArray(new X509Certificate[0]);
300 }
301 }