/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.transport.https; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; import java.security.GeneralSecurityException; import java.util.logging.Handler; import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.ReflectionInvokationHandler; import org.apache.cxf.common.util.ReflectionUtil; import org.apache.cxf.configuration.jsse.SSLUtils; import org.apache.cxf.configuration.jsse.TLSClientParameters; /** * This HttpsURLConnectionFactory implements the HttpURLConnectionFactory * for using the given SSL Policy to configure TLS connections for "https:" * URLs. */ public class HttpsURLConnectionFactory { /** * This constant holds the URL Protocol Identifier for HTTPS */ public static final String HTTPS_URL_PROTOCOL_ID = "https"; private static final Logger LOG = LogUtils.getL7dLogger(HttpsURLConnectionFactory.class); private static boolean weblogicWarned; /** * Cache the last SSLContext to avoid recreation */ SSLSocketFactory socketFactory; int lastTlsHash; /** * This constructor initialized the factory with the configured TLS * Client Parameters for the HTTPConduit for which this factory is used. */ public HttpsURLConnectionFactory() { } /** * Create a HttpURLConnection, proxified if necessary. * * * @param proxy This parameter is non-null if connection should be proxied. * @param url The target URL. * * @return The HttpURLConnection for the given URL. * @throws IOException */ public HttpURLConnection createConnection(TLSClientParameters tlsClientParameters, Proxy proxy, URL url) throws IOException { HttpURLConnection connection = (HttpURLConnection) (proxy != null ? url.openConnection(proxy) : url.openConnection()); if (HTTPS_URL_PROTOCOL_ID.equals(url.getProtocol())) { if (tlsClientParameters == null) { tlsClientParameters = new TLSClientParameters(); } try { decorateWithTLS(tlsClientParameters, connection); } catch (Throwable ex) { if (ex instanceof IOException) { throw (IOException) ex; } IOException ioException = new IOException("Error while initializing secure socket", ex); throw ioException; } } return connection; } /** * This method assigns the various TLS parameters on the HttpsURLConnection * from the TLS Client Parameters. Connection parameter is of supertype HttpURLConnection, * which allows internal cast to potentially divergent subtype (https) implementations. */ protected synchronized void decorateWithTLS(TLSClientParameters tlsClientParameters, HttpURLConnection connection) throws GeneralSecurityException { int hash = tlsClientParameters.hashCode(); if (hash != lastTlsHash) { lastTlsHash = hash; socketFactory = null; } // always reload socketFactory from HttpsURLConnection.defaultSSLSocketFactory and // tlsClientParameters.sslSocketFactory to allow runtime configuration change if (tlsClientParameters.isUseHttpsURLConnectionDefaultSslSocketFactory()) { socketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); } else if (tlsClientParameters.getSSLSocketFactory() != null) { // see if an SSLSocketFactory was set. This allows easy interop // with not-yet-commons-ssl.jar, or even just people who like doing their // own JSSE. socketFactory = tlsClientParameters.getSSLSocketFactory(); } else if (socketFactory == null) { // ssl socket factory not yet instantiated, create a new one with tlsClientParameters's Trust // Managers, Key Managers, etc SSLContext ctx = org.apache.cxf.transport.https.SSLUtils.getSSLContext(tlsClientParameters); String[] cipherSuites = SSLUtils.getCiphersuitesToInclude(tlsClientParameters.getCipherSuites(), tlsClientParameters.getCipherSuitesFilter(), ctx.getSocketFactory().getDefaultCipherSuites(), SSLUtils.getSupportedCipherSuites(ctx), LOG); // The SSLSocketFactoryWrapper enables certain cipher suites // from the policy. String protocol = tlsClientParameters.getSecureSocketProtocol() != null ? tlsClientParameters .getSecureSocketProtocol() : "TLS"; socketFactory = new SSLSocketFactoryWrapper(ctx.getSocketFactory(), cipherSuites, protocol); //recalc the hashcode since some of the above MAY have changed the tlsClientParameters lastTlsHash = tlsClientParameters.hashCode(); } else { // ssl socket factory already initialized, reuse it to benefit of keep alive } HostnameVerifier verifier = org.apache.cxf.transport.https.SSLUtils .getHostnameVerifier(tlsClientParameters); if (connection instanceof HttpsURLConnection) { // handle the expected case (javax.net.ssl) HttpsURLConnection conn = (HttpsURLConnection) connection; conn.setHostnameVerifier(verifier); conn.setSSLSocketFactory(socketFactory); } else { // handle the deprecated sun case and other possible hidden API's // that are similar to the Sun cases try { Method method = connection.getClass().getMethod("getHostnameVerifier"); InvocationHandler handler = new ReflectionInvokationHandler(verifier) { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return super.invoke(proxy, method, args); } catch (Exception ex) { return true; } } }; Object proxy = java.lang.reflect.Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {method.getReturnType()}, handler); method = connection.getClass().getMethod("setHostnameVerifier", method.getReturnType()); method.invoke(connection, proxy); } catch (Exception ex) { //Ignore this one } try { Method getSSLSocketFactory = connection.getClass().getMethod("getSSLSocketFactory"); Method setSSLSocketFactory = connection.getClass() .getMethod("setSSLSocketFactory", getSSLSocketFactory.getReturnType()); if (getSSLSocketFactory.getReturnType().isInstance(socketFactory)) { setSSLSocketFactory.invoke(connection, socketFactory); } else { //need to see if we can create one - mostly the weblogic case. The //weblogic SSLSocketFactory has a protected constructor that can take //a JSSE SSLSocketFactory so we'll try and use that Constructor<?> c = getSSLSocketFactory.getReturnType() .getDeclaredConstructor(SSLSocketFactory.class); ReflectionUtil.setAccessible(c); setSSLSocketFactory.invoke(connection, c.newInstance(socketFactory)); } } catch (Exception ex) { if (connection.getClass().getName().contains("weblogic")) { if (!weblogicWarned) { weblogicWarned = true; LOG.warning("Could not configure SSLSocketFactory on Weblogic. " + " Use the Weblogic control panel to configure the SSL settings."); } return; } //if we cannot set the SSLSocketFactory, we're in serious trouble. throw new IllegalArgumentException("Error decorating connection class " + connection.getClass().getName(), ex); } } } /* * For development and testing only */ protected void addLogHandler(Handler handler) { LOG.addHandler(handler); } }