001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.conn.ssl; 029 030import java.io.IOException; 031import java.io.InputStream; 032import java.net.InetSocketAddress; 033import java.net.Socket; 034import java.security.cert.Certificate; 035import java.security.cert.X509Certificate; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collection; 039import java.util.List; 040 041import javax.net.SocketFactory; 042import javax.net.ssl.HostnameVerifier; 043import javax.net.ssl.SSLContext; 044import javax.net.ssl.SSLHandshakeException; 045import javax.net.ssl.SSLPeerUnverifiedException; 046import javax.net.ssl.SSLSession; 047import javax.net.ssl.SSLSocket; 048import javax.security.auth.x500.X500Principal; 049 050import org.apache.commons.logging.Log; 051import org.apache.commons.logging.LogFactory; 052import org.apache.http.HttpHost; 053import org.apache.http.annotation.Contract; 054import org.apache.http.annotation.ThreadingBehavior; 055import org.apache.http.conn.socket.LayeredConnectionSocketFactory; 056import org.apache.http.conn.util.PublicSuffixMatcherLoader; 057import org.apache.http.protocol.HttpContext; 058import org.apache.http.ssl.SSLContexts; 059import org.apache.http.util.Args; 060import org.apache.http.util.TextUtils; 061 062/** 063 * Layered socket factory for TLS/SSL connections. 064 * <p> 065 * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of 066 * trusted certificates and to authenticate to the HTTPS server using a private key. 067 * <p> 068 * SSLSocketFactory will enable server authentication when supplied with 069 * a {@link java.security.KeyStore trust-store} file containing one or several trusted certificates. The client 070 * secure socket will reject the connection during the SSL session handshake if the target HTTPS 071 * server attempts to authenticate itself with a non-trusted certificate. 072 * <p> 073 * Use JDK keytool utility to import a trusted certificate and generate a trust-store file: 074 * <pre> 075 * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore 076 * </pre> 077 * <p> 078 * In special cases the standard trust verification process can be bypassed by using a custom 079 * {@link org.apache.http.conn.ssl.TrustStrategy}. This interface is primarily intended for allowing self-signed 080 * certificates to be accepted as trusted without having to add them to the trust-store file. 081 * <p> 082 * SSLSocketFactory will enable client authentication when supplied with 083 * a {@link java.security.KeyStore key-store} file containing a private key/public certificate 084 * pair. The client secure socket will use the private key to authenticate 085 * itself to the target HTTPS server during the SSL session handshake if 086 * requested to do so by the server. 087 * The target HTTPS server will in its turn verify the certificate presented 088 * by the client in order to establish client's authenticity. 089 * <p> 090 * Use the following sequence of actions to generate a key-store file 091 * </p> 092 * <ul> 093 * <li> 094 * <p> 095 * Use JDK keytool utility to generate a new key 096 * </p> 097 * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre> 098 * <p> 099 * For simplicity use the same password for the key as that of the key-store 100 * </p> 101 * </li> 102 * <li> 103 * <p> 104 * Issue a certificate signing request (CSR) 105 * </p> 106 * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre> 107 * </li> 108 * <li> 109 * <p> 110 * Send the certificate request to the trusted Certificate Authority for signature. 111 * One may choose to act as her own CA and sign the certificate request using a PKI 112 * tool, such as OpenSSL. 113 * </p> 114 * </li> 115 * <li> 116 * <p> 117 * Import the trusted CA root certificate 118 * </p> 119 * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre> 120 * </li> 121 * <li> 122 * <p> 123 * Import the PKCS#7 file containing the complete certificate chain 124 * </p> 125 * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre> 126 * </li> 127 * <li> 128 * <p> 129 * Verify the content of the resultant keystore file 130 * </p> 131 * <pre>keytool -list -v -keystore my.keystore</pre> 132 * </li> 133 * </ul> 134 * 135 * @since 4.3 136 */ 137@Contract(threading = ThreadingBehavior.SAFE) 138@SuppressWarnings("deprecation") 139public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactory { 140 141 public static final String TLS = "TLS"; 142 public static final String SSL = "SSL"; 143 public static final String SSLV2 = "SSLv2"; 144 145 @Deprecated 146 public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER 147 = AllowAllHostnameVerifier.INSTANCE; 148 149 @Deprecated 150 public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 151 = BrowserCompatHostnameVerifier.INSTANCE; 152 153 @Deprecated 154 public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER 155 = StrictHostnameVerifier.INSTANCE; 156 157 private final Log log = LogFactory.getLog(getClass()); 158 159 /** 160 * @since 4.4 161 */ 162 public static HostnameVerifier getDefaultHostnameVerifier() { 163 return new DefaultHostnameVerifier(PublicSuffixMatcherLoader.getDefault()); 164 } 165 166 /** 167 * Obtains default SSL socket factory with an SSL context based on the standard JSSE 168 * trust material ({@code cacerts} file in the security properties directory). 169 * System properties are not taken into consideration. 170 * 171 * @return default SSL socket factory 172 */ 173 public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException { 174 return new SSLConnectionSocketFactory(SSLContexts.createDefault(), getDefaultHostnameVerifier()); 175 } 176 177 private static String[] split(final String s) { 178 if (TextUtils.isBlank(s)) { 179 return null; 180 } 181 return s.split(" *, *"); 182 } 183 184 /** 185 * Obtains default SSL socket factory with an SSL context based on system properties 186 * as described in 187 * <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html"> 188 * Java™ Secure Socket Extension (JSSE) Reference Guide</a>. 189 * 190 * @return default system SSL socket factory 191 */ 192 public static SSLConnectionSocketFactory getSystemSocketFactory() throws SSLInitializationException { 193 return new SSLConnectionSocketFactory( 194 (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(), 195 split(System.getProperty("https.protocols")), 196 split(System.getProperty("https.cipherSuites")), 197 getDefaultHostnameVerifier()); 198 } 199 200 private final javax.net.ssl.SSLSocketFactory socketfactory; 201 private final HostnameVerifier hostnameVerifier; 202 private final String[] supportedProtocols; 203 private final String[] supportedCipherSuites; 204 205 public SSLConnectionSocketFactory(final SSLContext sslContext) { 206 this(sslContext, getDefaultHostnameVerifier()); 207 } 208 209 /** 210 * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLContext, 211 * javax.net.ssl.HostnameVerifier)} 212 */ 213 @Deprecated 214 public SSLConnectionSocketFactory( 215 final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) { 216 this(Args.notNull(sslContext, "SSL context").getSocketFactory(), 217 null, null, hostnameVerifier); 218 } 219 220 /** 221 * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLContext, 222 * String[], String[], javax.net.ssl.HostnameVerifier)} 223 */ 224 @Deprecated 225 public SSLConnectionSocketFactory( 226 final SSLContext sslContext, 227 final String[] supportedProtocols, 228 final String[] supportedCipherSuites, 229 final X509HostnameVerifier hostnameVerifier) { 230 this(Args.notNull(sslContext, "SSL context").getSocketFactory(), 231 supportedProtocols, supportedCipherSuites, hostnameVerifier); 232 } 233 234 /** 235 * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory, 236 * javax.net.ssl.HostnameVerifier)} 237 */ 238 @Deprecated 239 public SSLConnectionSocketFactory( 240 final javax.net.ssl.SSLSocketFactory socketfactory, 241 final X509HostnameVerifier hostnameVerifier) { 242 this(socketfactory, null, null, hostnameVerifier); 243 } 244 245 /** 246 * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory, 247 * String[], String[], javax.net.ssl.HostnameVerifier)} 248 */ 249 @Deprecated 250 public SSLConnectionSocketFactory( 251 final javax.net.ssl.SSLSocketFactory socketfactory, 252 final String[] supportedProtocols, 253 final String[] supportedCipherSuites, 254 final X509HostnameVerifier hostnameVerifier) { 255 this(socketfactory, supportedProtocols, supportedCipherSuites, (HostnameVerifier) hostnameVerifier); 256 } 257 258 /** 259 * @since 4.4 260 */ 261 public SSLConnectionSocketFactory( 262 final SSLContext sslContext, final HostnameVerifier hostnameVerifier) { 263 this(Args.notNull(sslContext, "SSL context").getSocketFactory(), 264 null, null, hostnameVerifier); 265 } 266 267 /** 268 * @since 4.4 269 */ 270 public SSLConnectionSocketFactory( 271 final SSLContext sslContext, 272 final String[] supportedProtocols, 273 final String[] supportedCipherSuites, 274 final HostnameVerifier hostnameVerifier) { 275 this(Args.notNull(sslContext, "SSL context").getSocketFactory(), 276 supportedProtocols, supportedCipherSuites, hostnameVerifier); 277 } 278 279 /** 280 * @since 4.4 281 */ 282 public SSLConnectionSocketFactory( 283 final javax.net.ssl.SSLSocketFactory socketfactory, 284 final HostnameVerifier hostnameVerifier) { 285 this(socketfactory, null, null, hostnameVerifier); 286 } 287 288 /** 289 * @since 4.4 290 */ 291 public SSLConnectionSocketFactory( 292 final javax.net.ssl.SSLSocketFactory socketfactory, 293 final String[] supportedProtocols, 294 final String[] supportedCipherSuites, 295 final HostnameVerifier hostnameVerifier) { 296 this.socketfactory = Args.notNull(socketfactory, "SSL socket factory"); 297 this.supportedProtocols = supportedProtocols; 298 this.supportedCipherSuites = supportedCipherSuites; 299 this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier(); 300 } 301 302 /** 303 * Performs any custom initialization for a newly created SSLSocket 304 * (before the SSL handshake happens). 305 * 306 * The default implementation is a no-op, but could be overridden to, e.g., 307 * call {@link javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])}. 308 * @throws IOException may be thrown if overridden 309 */ 310 protected void prepareSocket(final SSLSocket socket) throws IOException { 311 } 312 313 @Override 314 public Socket createSocket(final HttpContext context) throws IOException { 315 return SocketFactory.getDefault().createSocket(); 316 } 317 318 @Override 319 public Socket connectSocket( 320 final int connectTimeout, 321 final Socket socket, 322 final HttpHost host, 323 final InetSocketAddress remoteAddress, 324 final InetSocketAddress localAddress, 325 final HttpContext context) throws IOException { 326 Args.notNull(host, "HTTP host"); 327 Args.notNull(remoteAddress, "Remote address"); 328 final Socket sock = socket != null ? socket : createSocket(context); 329 if (localAddress != null) { 330 sock.bind(localAddress); 331 } 332 try { 333 if (connectTimeout > 0 && sock.getSoTimeout() == 0) { 334 sock.setSoTimeout(connectTimeout); 335 } 336 if (this.log.isDebugEnabled()) { 337 this.log.debug("Connecting socket to " + remoteAddress + " with timeout " + connectTimeout); 338 } 339 sock.connect(remoteAddress, connectTimeout); 340 } catch (final IOException ex) { 341 try { 342 sock.close(); 343 } catch (final IOException ignore) { 344 } 345 throw ex; 346 } 347 // Setup SSL layering if necessary 348 if (sock instanceof SSLSocket) { 349 final SSLSocket sslsock = (SSLSocket) sock; 350 this.log.debug("Starting handshake"); 351 sslsock.startHandshake(); 352 verifyHostname(sslsock, host.getHostName()); 353 return sock; 354 } else { 355 return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context); 356 } 357 } 358 359 @Override 360 public Socket createLayeredSocket( 361 final Socket socket, 362 final String target, 363 final int port, 364 final HttpContext context) throws IOException { 365 final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket( 366 socket, 367 target, 368 port, 369 true); 370 if (supportedProtocols != null) { 371 sslsock.setEnabledProtocols(supportedProtocols); 372 } else { 373 // If supported protocols are not explicitly set, remove all SSL protocol versions 374 final String[] allProtocols = sslsock.getEnabledProtocols(); 375 final List<String> enabledProtocols = new ArrayList<String>(allProtocols.length); 376 for (final String protocol: allProtocols) { 377 if (!protocol.startsWith("SSL")) { 378 enabledProtocols.add(protocol); 379 } 380 } 381 if (!enabledProtocols.isEmpty()) { 382 sslsock.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()])); 383 } 384 } 385 if (supportedCipherSuites != null) { 386 sslsock.setEnabledCipherSuites(supportedCipherSuites); 387 } 388 389 if (this.log.isDebugEnabled()) { 390 this.log.debug("Enabled protocols: " + Arrays.asList(sslsock.getEnabledProtocols())); 391 this.log.debug("Enabled cipher suites:" + Arrays.asList(sslsock.getEnabledCipherSuites())); 392 } 393 394 prepareSocket(sslsock); 395 this.log.debug("Starting handshake"); 396 sslsock.startHandshake(); 397 verifyHostname(sslsock, target); 398 return sslsock; 399 } 400 401 private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException { 402 try { 403 SSLSession session = sslsock.getSession(); 404 if (session == null) { 405 // In our experience this only happens under IBM 1.4.x when 406 // spurious (unrelated) certificates show up in the server' 407 // chain. Hopefully this will unearth the real problem: 408 final InputStream in = sslsock.getInputStream(); 409 in.available(); 410 // If ssl.getInputStream().available() didn't cause an 411 // exception, maybe at least now the session is available? 412 session = sslsock.getSession(); 413 if (session == null) { 414 // If it's still null, probably a startHandshake() will 415 // unearth the real problem. 416 sslsock.startHandshake(); 417 session = sslsock.getSession(); 418 } 419 } 420 if (session == null) { 421 throw new SSLHandshakeException("SSL session not available"); 422 } 423 424 if (this.log.isDebugEnabled()) { 425 this.log.debug("Secure session established"); 426 this.log.debug(" negotiated protocol: " + session.getProtocol()); 427 this.log.debug(" negotiated cipher suite: " + session.getCipherSuite()); 428 429 try { 430 431 final Certificate[] certs = session.getPeerCertificates(); 432 final X509Certificate x509 = (X509Certificate) certs[0]; 433 final X500Principal peer = x509.getSubjectX500Principal(); 434 435 this.log.debug(" peer principal: " + peer.toString()); 436 final Collection<List<?>> altNames1 = x509.getSubjectAlternativeNames(); 437 if (altNames1 != null) { 438 final List<String> altNames = new ArrayList<String>(); 439 for (final List<?> aC : altNames1) { 440 if (!aC.isEmpty()) { 441 altNames.add((String) aC.get(1)); 442 } 443 } 444 this.log.debug(" peer alternative names: " + altNames); 445 } 446 447 final X500Principal issuer = x509.getIssuerX500Principal(); 448 this.log.debug(" issuer principal: " + issuer.toString()); 449 final Collection<List<?>> altNames2 = x509.getIssuerAlternativeNames(); 450 if (altNames2 != null) { 451 final List<String> altNames = new ArrayList<String>(); 452 for (final List<?> aC : altNames2) { 453 if (!aC.isEmpty()) { 454 altNames.add((String) aC.get(1)); 455 } 456 } 457 this.log.debug(" issuer alternative names: " + altNames); 458 } 459 } catch (final Exception ignore) { 460 } 461 } 462 463 if (!this.hostnameVerifier.verify(hostname, session)) { 464 final Certificate[] certs = session.getPeerCertificates(); 465 final X509Certificate x509 = (X509Certificate) certs[0]; 466 final List<SubjectName> subjectAlts = DefaultHostnameVerifier.getSubjectAltNames(x509); 467 throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " + 468 "of the subject alternative names: " + subjectAlts); 469 } 470 // verifyHostName() didn't blowup - good! 471 } catch (final IOException iox) { 472 // close the socket before re-throwing the exception 473 try { sslsock.close(); } catch (final Exception x) { /*ignore*/ } 474 throw iox; 475 } 476 } 477 478}