blob: 784342314b1a0533c0d39c5b9f14f1f9b4f26103 [file] [log] [blame]
/*
* 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.synapse.transport.nhttp;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.nio.NHttpClientEventHandler;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.ssl.SSLSetupHandler;
import org.apache.axis2.AxisFault;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.transport.base.ParamUtils;
import org.apache.axiom.om.OMElement;
import org.apache.synapse.transport.utils.logging.LoggingUtils;
import org.apache.synapse.transport.utils.sslcert.CertificateVerificationConfig;
import org.apache.synapse.transport.utils.sslcert.CertificateVerificationException;
import org.apache.synapse.transport.utils.sslcert.adaptor.SynapseAdaptorForOcspCrl;
import javax.net.ssl.*;
import javax.xml.namespace.QName;
import java.util.Map;
import java.util.Iterator;
import java.util.HashMap;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.GeneralSecurityException;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
public class HttpCoreNIOSSLSender extends HttpCoreNIOSender {
private static final Log log = LogFactory.getLog(HttpCoreNIOSSLSender.class);
private static final SynapseAdaptorForOcspCrl ocspCrl = new SynapseAdaptorForOcspCrl();
protected IOEventDispatch getEventDispatch(NHttpClientEventHandler handler, SSLContext sslContext,
SSLSetupHandler sslSetupHandler, ConnectionConfig config,
TransportOutDescription transportOut) throws AxisFault {
return LoggingUtils.getClientIODispatch(handler, config, sslContext,
sslSetupHandler, getCustomSSLContexts(transportOut));
}
/**
* Create the SSLContext to be used by this sender
*
* @param transportOut the Axis2 transport configuration
* @return the SSLContext to be used
*/
protected SSLContext getSSLContext(TransportOutDescription transportOut) throws AxisFault {
Parameter keyParam = transportOut.getParameter("keystore");
Parameter trustParam = transportOut.getParameter("truststore");
OMElement ksEle = null;
OMElement tsEle = null;
if (keyParam != null) {
ksEle = keyParam.getParameterElement().getFirstElement();
}
boolean novalidatecert = ParamUtils.getOptionalParamBoolean(transportOut,
"novalidatecert", false);
if (trustParam != null) {
if (novalidatecert) {
log.warn("Ignoring novalidatecert parameter since a truststore has been specified");
}
tsEle = trustParam.getParameterElement().getFirstElement();
}
return createSSLContext(ksEle, tsEle, novalidatecert);
}
/**
* Create the SSLIOSessionHandler to initialize the host name verification at the following
* levels, through an Axis2 transport configuration parameter as follows:
* HostnameVerifier - Default, DefaultAndLocalhost, Strict, AllowAll
*
* @param transportOut the Axis2 transport configuration
* @return the SSLIOSessionHandler to be used
* @throws AxisFault if a configuration error occurs
*/
protected SSLSetupHandler getSSLSetupHandler(TransportOutDescription transportOut)
throws AxisFault {
Parameter hostnameVerifier = transportOut.getParameter("HostnameVerifier");
String hostnameVerifierValue = hostnameVerifier != null ?
hostnameVerifier.getValue().toString() : null;
Parameter revocationVerifierParam = transportOut.getParameter("CertificateRevocationVerifier");
CertificateVerificationConfig cvConfig = revocationVerifierParam != null ?
new CertificateVerificationConfig(revocationVerifierParam) : null;
return createSSLSetupHandler(hostnameVerifierValue, cvConfig);
}
/**
* Looks for a transport parameter named customSSLProfiles and initializes zero or more
* custom SSLContext instances. The syntax for defining custom SSL profiles is as follows.
*
* <parameter name="customSSLProfiles>
* <profile>
* <servers>www.test.org:80, www.test2.com:9763</servers>
* <KeyStore>
* <Location>/path/to/identity/store</Location>
* <Type>JKS</Type>
* <Password>password</Password>
* <KeyPassword>password</KeyPassword>
* </KeyStore>
* <TrustStore>
* <Location>path/tp/trust/store</Location>
* <Type>JKS</Type>
* <Password>password</Password>
* </TrustStore>
* </profile>
* </parameter>
*
* Any number of profiles can be defined under the customSSLProfiles parameter.
*
* @param transportOut transport out description
* @return a map of server addresses and SSL contexts
* @throws AxisFault if at least on SSL profile is not properly configured
*/
private Map<String, SSLContext> getCustomSSLContexts(TransportOutDescription transportOut)
throws AxisFault {
if (log.isDebugEnabled()) {
log.info("Loading custom SSL profiles for the HTTPS sender");
}
Parameter customProfilesParam = transportOut.getParameter("customSSLProfiles");
if (customProfilesParam == null) {
return null;
}
OMElement customProfilesElt = customProfilesParam.getParameterElement();
Iterator profiles = customProfilesElt.getChildrenWithName(new QName("profile"));
Map<String, SSLContext> contextMap = new HashMap<String, SSLContext>();
while (profiles.hasNext()) {
OMElement profile = (OMElement) profiles.next();
OMElement serversElt = profile.getFirstChildWithName(new QName("servers"));
if (serversElt == null || serversElt.getText() == null) {
String msg = "Each custom SSL profile must define at least one host:port " +
"pair under the servers element";
log.error(msg);
throw new AxisFault(msg);
}
String[] servers = serversElt.getText().split(",");
OMElement ksElt = profile.getFirstChildWithName(new QName("KeyStore"));
OMElement trElt = profile.getFirstChildWithName(new QName("TrustStore"));
String noValCert = profile.getAttributeValue(new QName("novalidatecert"));
boolean novalidatecert = "true".equals(noValCert);
SSLContext sslContext = createSSLContext(ksElt, trElt, novalidatecert);
for (String server : servers) {
server = server.trim();
if (!contextMap.containsKey(server)) {
contextMap.put(server, sslContext);
} else {
log.warn("Multiple SSL profiles were found for the server : " + server + ". " +
"Ignoring the excessive profiles.");
}
}
}
if (contextMap.size() > 0) {
log.info("Custom SSL profiles initialized for " + contextMap.size() + " servers");
return contextMap;
}
return null;
}
private SSLContext createSSLContext(OMElement keyStoreElt, OMElement trustStoreElt,
boolean novalidatecert) throws AxisFault {
KeyManager[] keymanagers = null;
TrustManager[] trustManagers = null;
if (keyStoreElt != null) {
String location = keyStoreElt.getFirstChildWithName(new QName("Location")).getText();
String type = keyStoreElt.getFirstChildWithName(new QName("Type")).getText();
String storePassword = keyStoreElt.getFirstChildWithName(new QName("Password")).getText();
String keyPassword = keyStoreElt.getFirstChildWithName(new QName("KeyPassword")).getText();
FileInputStream fis = null;
try {
KeyStore keyStore = KeyStore.getInstance(type);
fis = new FileInputStream(location);
log.info("Loading Identity Keystore from : " + location);
keyStore.load(fis, storePassword.toCharArray());
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmfactory.init(keyStore, keyPassword.toCharArray());
keymanagers = kmfactory.getKeyManagers();
} catch (GeneralSecurityException gse) {
log.error("Error loading Keystore : " + location, gse);
throw new AxisFault("Error loading Keystore : " + location, gse);
} catch (IOException ioe) {
log.error("Error opening Keystore : " + location, ioe);
throw new AxisFault("Error opening Keystore : " + location, ioe);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException ignore) {}
}
}
}
if (trustStoreElt != null) {
if (novalidatecert) {
log.warn("Ignoring novalidatecert parameter since a truststore has been specified");
}
String location = trustStoreElt.getFirstChildWithName(new QName("Location")).getText();
String type = trustStoreElt.getFirstChildWithName(new QName("Type")).getText();
String storePassword = trustStoreElt.getFirstChildWithName(new QName("Password")).getText();
FileInputStream fis = null;
try {
KeyStore trustStore = KeyStore.getInstance(type);
fis = new FileInputStream(location);
log.info("Loading Trust Keystore from : " + location);
trustStore.load(fis, storePassword.toCharArray());
TrustManagerFactory trustManagerfactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerfactory.init(trustStore);
trustManagers = trustManagerfactory.getTrustManagers();
} catch (GeneralSecurityException gse) {
log.error("Error loading Key store : " + location, gse);
throw new AxisFault("Error loading Key store : " + location, gse);
} catch (IOException ioe) {
log.error("Error opening Key store : " + location, ioe);
throw new AxisFault("Error opening Key store : " + location, ioe);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException ignore) {}
}
}
} else if (novalidatecert) {
log.warn("Server certificate validation (trust) has been disabled. " +
"DO NOT USE IN PRODUCTION!");
trustManagers = new TrustManager[] { new NoValidateCertTrustManager() };
}
try {
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(keymanagers, trustManagers, null);
return sslcontext;
} catch (GeneralSecurityException gse) {
log.error("Unable to create SSL context with the given configuration", gse);
throw new AxisFault("Unable to create SSL context with the given configuration", gse);
}
}
private SSLSetupHandler createSSLSetupHandler(final String hostnameVerifier,
final CertificateVerificationConfig cvConfig) throws AxisFault {
return new SSLSetupHandler() {
public void initalize(SSLEngine sslengine) {
}
public void verify(IOSession ioSession, SSLSession session) throws SSLException {
SocketAddress remoteAddress = ioSession.getRemoteAddress();
String address;
if (remoteAddress instanceof InetSocketAddress) {
address = ((InetSocketAddress) remoteAddress).getHostName();
} else {
address = remoteAddress.toString();
}
boolean valid = false;
//Do HostName verification.
if (hostnameVerifier != null) {
if ("Strict".equals(hostnameVerifier)) {
valid = HostnameVerifier.STRICT.verify(address, session);
} else if ("AllowAll".equals(hostnameVerifier)) {
valid = HostnameVerifier.ALLOW_ALL.verify(address, session);
} else if ("DefaultAndLocalhost".equals(hostnameVerifier)) {
valid = HostnameVerifier.DEFAULT_AND_LOCALHOST.verify(address, session);
}
} else {
valid = HostnameVerifier.DEFAULT.verify(address, session);
}
if (!valid) {
throw new SSLException("Host name verification failed for host : " + address);
}
if (cvConfig != null) {
try {
ocspCrl.verifyRevocationStatus(session.getPeerCertificateChain(),
cvConfig.getCacheSize(), cvConfig.getCacheDuration());
} catch (CertificateVerificationException e) {
throw new SSLException("Certificate chain validation failed for host : " +
address, e);
}
}
}
};
}
}