/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.soa.esb.http.configurators;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Properties;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.ProxyHost;
import org.apache.commons.httpclient.contrib.ssl.StrictSSLProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.ssl.KeyMaterial;
import org.apache.commons.ssl.SSLClient;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.http.Configurator;
import org.jboss.soa.esb.http.protocol.ProtocolSocketFactoryBuilder;
import org.jboss.soa.esb.util.ClassUtil;
/**
* HTTP Protocol configurator.
* <p/>
* Supports both HTTP and HTTPS, including configuring the socket factory and SSL keystore. It
* supports the SSL keystore being on the classpath, filesystem or based on a {@link URI}.
* <p/>
* Properties:
* <ul>
* <li><b>target-host-url</b>: (Required)</li>
* <li><b>keystore</b>: (Optional). See {@link KeyMaterial}. Defaults to "/keystore".</li>
* <li><b>keystore-passwd</b>: (Optional). See {@link KeyMaterial}. Defaults to "changeit".</li>
* <li><b>protocol-socket-factory</b>: See {@link Protocol}. (Optional).
* Defaults to {@link StrictSSLProtocolSocketFactory} for HTTPS, otherwise defaults to {@link DefaultProtocolSocketFactory}.
* Configure with {@link org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory} for unauthenticated
* SSL. To authenticate with a self-signed certificate, use the
* {@link org.jboss.soa.esb.http.protocol.SelfSignedSSLProtocolSocketFactoryBuilder} (test/demo only) and to perform
* full authentication (2way) with CA Signed certs, use the {@link org.jboss.soa.esb.http.protocol.AuthSSLProtocolSocketFactoryBuilder}.</li>
* </ul>
* <p/>
* See <a href="http://jakarta.apache.org/commons/httpclient/sslguide.html">HttpClient HttpProtocol Guide</a>.
*
* @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
*/
public class HttpProtocol extends Configurator {
private Logger log = Logger.getLogger(HttpProtocol.class);
public void configure(HttpClient httpClient, Properties properties) throws ConfigurationException {
URI targetURI = getTargetURI(properties, true);
String factory;
String scheme = targetURI.getScheme();
int port = targetURI.getPort();
org.apache.commons.httpclient.protocol.Protocol protocol;
KeyMaterial keyMaterial = null;
ProtocolSocketFactory socketFactory;
if(!scheme.startsWith("http")) {
// We're only interested in HTTP for this...
return;
}
final int defaultPort ;
boolean secure = "https".equals(scheme);
if (secure) {
factory = properties.getProperty("protocol-socket-factory", StrictSSLProtocolSocketFactory.class.getName());
keyMaterial = getKeyMaterial(properties);
defaultPort = 443;
} else {
factory = properties.getProperty("protocol-socket-factory", DefaultProtocolSocketFactory.class.getName());
defaultPort = 80;
}
if (port <= 0) {
port = defaultPort ;
}
assertPropertySetAndNotBlank(factory, "protocol-socket-factory");
socketFactory = createFactoryClass(factory, keyMaterial, properties);
// And finally... configure the host with the protocol....
protocol = new Protocol(scheme, socketFactory, defaultPort);
// these lines have to happen after registerProtocol, otherwise they pick up the wrong static value
if (secure) {
setHttpsProxyHost(httpClient, properties);
} else {
setHttpProxyHost(httpClient, properties);
}
// Use our own protocol, see JBESB-3021 comments for more information.
httpClient.getHostConfiguration().setHost(targetURI.getHost(), port, protocol);
}
/**
* Will create a ProxyHost using the properties 'http.proxyHost' and optionally
* the property 'http.proxyPort'.
*
* @param httpClient The HttpClient that should have its host configurations proxy host set.
* @param properties The properties that should be set.
*/
void setHttpProxyHost(final HttpClient httpClient, final Properties properties) {
ProxyHost proxyHost = createProxyHost("http.proxyHost", "http.proxyPort", 80, properties);
if (proxyHost != null) {
httpClient.getHostConfiguration().setProxyHost(proxyHost);
}
}
/**
* Will create a ProxyHost using the properties 'https.proxyHost' and optionally
* the property 'https.proxyPort'.
*
* @param httpClient The HttpClient that should have its host configurations proxy host set.
* @param properties The properties that should be set.
*/
void setHttpsProxyHost(final HttpClient httpClient, final Properties properties) {
ProxyHost proxyHost = createProxyHost("https.proxyHost", "https.proxyPort", 443, properties);
if (proxyHost != null) {
httpClient.getHostConfiguration().setProxyHost(proxyHost);
}
}
private ProxyHost createProxyHost(final String hostPropertyName, final String portPropertyName, final int defaultPort, final Properties properties) {
final String proxyHost = (String) properties.get(hostPropertyName);
ProxyHost proxy = null;
if (proxyHost != null) {
final String proxyPortStr = (String) properties.get(portPropertyName);
if (proxyPortStr == null) {
proxy = new ProxyHost(proxyHost, defaultPort);
}
else {
proxy = new ProxyHost(proxyHost, Integer.parseInt(proxyPortStr));
}
log.debug("ProxyHost " + proxy.toString());
}
return proxy;
}
private KeyMaterial getKeyMaterial(Properties properties) throws ConfigurationException {
String keyStore = properties.getProperty("keystore", "/keystore");
String keyStorePassword = properties.getProperty("keystore-passw", "changeit");
keyStorePassword = getPasswordFromFile(keyStorePassword);
// Try it as a classpath resource ...
InputStream keyStoreStream = ClassUtil.getResourceAsStream(keyStore, HttpProtocol.class);
try {
// Try it as a File resource...
if(keyStoreStream == null) {
File keyStoreFile = new File(keyStore);
if(keyStoreFile.exists() && !keyStoreFile.isDirectory()) {
return new KeyMaterial(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
}
} else {
return new KeyMaterial(keyStoreStream, keyStorePassword.toCharArray());
}
// Try it as a URI resource...
if(keyStoreStream == null) {
try {
URI fileURI = new URI(keyStore);
if(fileURI.isAbsolute()) {
return new KeyMaterial(fileURI.toURL().openStream(), keyStorePassword.toCharArray());
}
} catch (URISyntaxException e) {
throw new ConfigurationException("Failed to load keystore '" + keyStore + "'.");
}
}
} catch (IOException e) {
throw new ConfigurationException("Failed to load keystore '" + keyStore + "'.", e);
} catch (GeneralSecurityException e) {
throw new ConfigurationException("Failed to load keystore '" + keyStore + "'.", e);
}
throw new ConfigurationException("Failed to locate keystore '" + keyStore + "'.");
}
private ProtocolSocketFactory createFactoryClass(String factory, KeyMaterial keyMaterial, Properties properties) throws ConfigurationException {
ProtocolSocketFactory socketFactory = null;
try {
Class<?> factoryClass = ClassUtil.forName(factory, HttpProtocol.class);
if(ProtocolSocketFactoryBuilder.class.isAssignableFrom(factoryClass)) {
try {
ProtocolSocketFactoryBuilder factoryBuilder = (ProtocolSocketFactoryBuilder) factoryClass.newInstance();
factoryBuilder.setConfiguration(properties);
socketFactory = factoryBuilder.newInstance();
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to instantiate ProtocolSocketFactoryBuilder implementation class [" + factory + "]. Must provide a default constructor.", e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to instantiate ProtocolSocketFactoryBuilder implementation class [" + factory + "]. Must provide a default constructor.", e);
}
} else {
socketFactory = (ProtocolSocketFactory) factoryClass.newInstance();
}
} catch (ClassCastException e) {
throw new ConfigurationException("Class [" + factory + "] must implement [" + ProtocolSocketFactory.class.getName() + "].", e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("ProtocolSocketFactory implementation class [" + factory + "] not found in classpath.", e);
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to instantiate ProtocolSocketFactory implementation class [" + factory + "].", e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to instantiate ProtocolSocketFactory implementation class [" + factory + "].", e);
}
if(socketFactory instanceof SSLClient && keyMaterial != null) {
try {
((SSLClient)socketFactory).setKeyMaterial(keyMaterial);
} catch (NoSuchAlgorithmException e) {
throw new ConfigurationException("Failed to configure SSL Keystore on SSLClient.", e);
} catch (KeyStoreException e) {
throw new ConfigurationException("Failed to configure SSL Keystore on SSLClient.", e);
} catch (KeyManagementException e) {
throw new ConfigurationException("Failed to configure SSL Keystore on SSLClient.", e);
} catch (IOException e) {
throw new ConfigurationException("Failed to configure SSL Keystore on SSLClient.", e);
} catch (CertificateException e) {
throw new ConfigurationException("Failed to configure SSL Keystore on SSLClient.", e);
}
}
return socketFactory;
}
}