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