Friday, August 24, 2012

Migration to JBoss 7.1 (1 part)

My current project migrate from JBoss 5.1 to JBoss 7.1 This migration caused by best moment to do this. So first attempt to migrate to JBoss 7.0 was in the begging of this year but this migration fails. Main problem was in to small time period for migration and work was done on my personal initiative. This time project has JSF UI part that work under MyFaces implementation and this was the thin place that produces many problems. Nowadays web UI transfered to tomcat server instance and conflicts between JSF implementation make no affect to migration. But many problems has been found during migration. So this is one of series of post about the features of 7.1 version.

First few words about project. This is a e-commerce platform. Technically platform includes 5 ear applications with big common part. There are strong requirements due security so some parts of application configurations (also configuration to database) are encrypted and this requirement rises problems. I start with little introducing to JBoss 7.1. JBoss 7.1 based on module system and this provides so effective server startup. You personally choose what modules (libraries) should be used. Particularly, JBoss developer team cut classes from JDK. So if application needed special classes from sun packages or others you should write them manually.
Read more Modules placed into ${JBOSS_HOME}/modules folder and structure like Java packages. So in ${JBOSS_HOME}/sun/jdk/main placed module for special JDK classes. Each package should has one slot ("main" slot by default) like in sun.jdk package you can place folder "other" and declare another classes that application needs and use "main" slot for one application and "other" for another. Declaration of module placed in module.xml file (one for each slot). Here is an example of httpcomponents module:


<module name="org.apache.httpcomponents" xmlns="urn:jboss:module:1.1">
    <properties>
        <property name="jboss.api" value="private"/>
    </property>

    <resources>
        <resource-root path="httpclient-4.1.2.jar"/>
        <resource-root path="httpcore-4.1.4.jar"/>
        <resource-root path="httpmime-4.1.2.jar"/>
        <!-- Insert resources here -->
    </resource>

    <dependencies>
        <module name="javax.api"/>
        <module name="org.apache.commons.codec"/>
        <module name="org.apache.commons.logging"/>
        <module name="org.apache.james.mime4j"/>
    </dependencies>
</module>

In "resources" section placed definition of jar's included in module. In "dependencies" placed dependencies for this module. So if in jars used any classes which not from jre or dependencies libraries there will be ClassNotFoundException in runtime. if you place in dependency module declaration attribute export="true" than dependency module will be added to current module (this is for transitive module dependencies). <> In sun.jdk module you can find another module.xml declaraton schema:

<module name="sun.jdk" xmlns="urn:jboss:module:1.1">
    <resources>
        <!-- currently jboss modules has not way of importing services from
        classes.jar so we duplicate them here -->
        <resource-root path="service-loader-resources">
    </resource>
    <dependencies>
        <system export="true">
            <paths>
                <path name="com/sun/script/javascript"/>
                <path name="com/sun/jndi/dns"/>
                <path name="com/sun/jndi/ldap"/>
                <path name="com/sun/jndi/url"/>
                <path name="com/sun/jndi/url/dns"/>
                <path name="com/sun/security/auth"/>
                <path name="com/sun/security/auth/login"/>
                <path name="com/sun/security/auth/module"/>
                <path name="sun/misc"/>
                <path name="sun/io"/>
                <path name="sun/nio"/>
                <path name="sun/nio/ch"/>
                <path name="sun/security"/>
                <path name="sun/security/krb5"/>
                <path name="sun/util"/>
                <path name="sun/util/calendar"/>
                <path name="sun/util/locale"/>
                <path name="sun/security/provider"/>
                <path name="META-INF/services"/>
            </paths>
            <exports>
                <include -set="-set"/>
                    <path name="META-INF/services"/>
                </include/>
            </exports>
        </system>
    </dependencies>
</module>
Here is declaration for packages from JDK that will be exproted in runtime. More detailed information available here So in the begging we have 5 ear application, project common libraries places with external libraries in ${JBOSS_HOME}/server/default/deploy/lib folder of JBoss 5.1, applications configurations (in xml files, some of them is encrypted). The easiest way to port this libraries is to place them into one modules (we decide to place project common libraries in one module and other tools-libraries into another). The next step is to write dependencies for modules. Here is the begging of evil. Thanks to opensource projects that there is source code that can be checked out for what happens there. Best situation when you received in runtime ClassNotFoundException but in sometimes libraries such as XStream produce so strange exceptions that if there is no source code you will never get what you should do. For example like this:

Caused by: java.lang.NullPointerException
at com.thoughtworks.xstream.mapper.ClassAliasingMapper.addClassAlias(ClassAliasingMapper.java:44) [xstream-1.3.1.jar:]
at com.thoughtworks.xstream.XStream.alias(XStream.java:939) [xstream-1.3.1.jar:]
at com.thoughtworks.xstream.XStream.setupAliases(XStream.java:588) [xstream-1.3.1.jar:]
at com.thoughtworks.xstream.XStream.(XStream.java:443) [xstream-1.3.1.jar:]
at com.thoughtworks.xstream.XStream.(XStream.java:385) [xstream-1.3.1.jar:]
at com.thoughtworks.xstream.XStream.(XStream.java:323) [xstream-1.3.1.jar:]

If we check source code of XStream class, we see:

        if (JVM.is14()) {
            alias("auth-subject", jvm.loadClass("javax.security.auth.Subject"));
            alias("linked-hash-map", jvm.loadClass("java.util.LinkedHashMap"));
            alias("linked-hash-set", jvm.loadClass("java.util.LinkedHashSet"));
            alias("trace", jvm.loadClass("java.lang.StackTraceElement"));
            alias("currency", jvm.loadClass("java.util.Currency"));
            aliasType("charset", jvm.loadClass("java.nio.charset.Charset"));
        }
What a surprise, we tried to load JDK class "javax.security.auth.Subject" that not included into JDK module.
I assume to write next post about the configuration of all server. Thats mean explanation about the standalone.xml (I don't think that in the closest future I work with domain of jbosses so now only about stanalone :)

Saturday, March 3, 2012

Java SOAP implementations with ssl

Later I wrote about using http and https protocols in Java. Now I describe how can be soap protocol can be implemented undeer http and https. Java has few implementations of soap protocol:
  • Apache Axis2 implementation.
  • Apache CXF - another iplementation of soap using in jboss (there are similar implementation - JBossWS)
  • JAX-WS - standart java implementation.
Apache axis and axis2 is the first implementation of soap in Java, because of this I will omit them.

Implementation devides into two parts: server part and client part. In server part all goes simple - the problem is to make right configuration of your aplication server and deploy service under configured protocol. But client part seems not so easy. First you should know what soap implementation used in application environment and applying ssl setting will depends on implementation.
Read more The standart practice in writing web-services clients is to create Java classes and main interface by tool provided by enviroment. For example if application deployed under GlassFish or run as standalone java process then wsimport (java service from jdk) should be used. Other wise if application deployed under JBoss then prefer to use wsconsume. For CXF can be used wsdl2java tool. So you get generated java interface, instances mapped to xml by JAXB (or XMLBeans) and javax.xml.ws.Service child. For getting client you should instantiate this child class and then call getPort method. Let consider simple example let our server have some Processing web-service which start some process. Here is an interface:
package org.lehvolk.common.ws.example;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService(targetNamespace = "http://emample.lehvolk.org/", name = "Processing")
public interface ProcessingBean {

 @WebResult(targetNamespace = "http://emample.lehvolk.org/process")
 @WebMethod(operationName = "process", action = "http://emample.lehvolk.org/process")
 public ProcessOutput process(@WebParam ProcessInput input);
}
Here is service class:
package org.lehvolk.common.ws.example;

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;

@WebServiceClient(name = "Processing",
  wsdlLocation = ProcessingService.WSDL_LOCATION,
  targetNamespace = "http://example.lehvolk.org/")
public class ProcessingService extends Service {

 public final static String WSDL_LOCATION = "META-INF/wsdl/service.wsdl";
 public final static QName SERVICE = new QName("http://example.lehvolk.org", "ProcessingService");
 public final static QName Processing = new QName("http://example.lehvolk.org", "Processing");

 public ProcessingService() {
  super(getWsdlLocation(), SERVICE);
 }

 /**
  * @return returns Management
  */
 @WebEndpoint(name = "Processing")
 public ProcessingBean getPort() {
  return super.getPort(Processing, ProcessingBean.class);
 }

 private static URL getWsdlLocation() {
  return ProcessingService.class.getResource(WSDL_LOCATION);
 }
}
For web-service client we should create new instance of ProcessingService and then call getPort method. Actually getPort method return some proxy-instance. ProcessingService class is thread-safe so it's enought to have one instance of Service in your application. Proxy-objects returned from getPort method have more difficult structure and they not thread-safe. So you shouldn't use one instance of this proxy from more than one thread. Configurating port for ssl is expensive operation too. That means that best choise is to store port-instances in some cache (I will use ehcache for this purposes). We need to garantee that port instance will not be used in another thread., i.e. we should in one thread get and remove instance from cache and after making call to remote web-service put it back in cache. Here is wrapper of port instance:
package org.lehvolk.common.ws;

/**
 * Wrapper of web-service port
 * 
 * @param <T> - type of port
 */
public class WebServicePort<T> {

 private final T port;
 private final String soapVersion;
 private final String address;

 /**
  * Constructs instance with parameters specified
  * 
  * @param port - web-service port
  * @param soapVersion - version of soap protocol
  * @param address - web-service address
  */
 public WebServicePort(T port, String soapVersion, String address) {
  this.port = port;
  this.soapVersion = soapVersion;
  this.address = address;
 }

 // getters
}
Here is interface to ports pool:
package org.lehvolk.common.ws.pool;

import org.lehvolk.common.ws.WebServicePort;

/**
 * Interface of WS ports pool
 * 
 * @param <T> - generic type of port
 */
public interface WebServicePortPool<T> {

 /**
  * get port by address from configuration
  * 
  * @return instance of port
  */
 public WebServicePort<T> getPort();

 /**
  * @param address - web-service address
  * @return port for given address
  */
 public WebServicePort<T> getPort(String address);

 /**
  * Initialize service
  */
 public void postConstruct();

 /**
  * @param port - web-service port
  */
 public void putPort(WebServicePort<T> port);

 /**
  * Reinitialize pool with new configuration
  */
 public void reset();

 /**
  * Shutdowns pool
  */
 public void shutdown();
}
And here there is interface for configuraion port instance:
package org.lehvolk.common.ws;

import javax.net.ssl.SSLSocketFactory;

/**
 * Interface of class, for configuration web-service port, e.g. timeout, security aspects.
 */
public interface WSConfigurator {

 /**
  * Configures web-service port
  * 
  * @param <T> - type of port
  * @param port - web-service port
  * @param wsAddress - address of web-service
  * @param connTimeout - connection timeout
  * @param readTimeout - socket read timeout
  * @param sf - {@link SSLSocketFactory} instance
  * @param verifyHost - host verification enabled
  * @return - configured web-service port
  */
 public <T> T configurePort(T port, String wsAddress, long connTimeout, long readTimeout, SSLSocketFactory sf,
   boolean verifyHost);

}
Ports pool should encapsulate configure logic on port creation (configure ssl connection, ws address and pool config). Configuration should have ssl configuration, common confiuration (addresses, timeouts) and cache configuration. I will use SSLConfiguration (and SSLUtils for generation SSLCOntext) from this post. And here is configuration for other instances (as always this classes can be stored in xml).
package org.lehvolk.common.ws.pool;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * Configuration of WS-ports pool
 */
@XmlRootElement(name = "ports-pool-configuration")
@XmlAccessorType(XmlAccessType.FIELD)
public class PortsPoolConfiguration{

 @XmlElement(name = "cache-name", required = true)
 private String cacheName;
 @XmlElement(name = "pool-size", required = false)
 private Integer poolSize = 100;

 //getters and setters
}
package org.lehvolk.common.ws.pool;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.lehvolk.common.ssl.SSLConfiguration;

/**
 * Configuration of web-service clients
 */
@XmlRootElement(name = "ws-client-configuration")
@XmlAccessorType(XmlAccessType.FIELD)
public class WSClientConfiguration {

 @XmlElement(name = "connection-timeout", required = false)
 private Long connectionTimeout = 30 * 1000L; //30 seconds

 @XmlElement(name = "ws-address", required = true)
 private String wsAddress;

 @XmlElement(name = "pool-config", required = true)
 private PortsPoolConfiguration poolConfig;

 @XmlElement(name = "socket-read-timeout", required = false)
 private Long socketReadTimeout = 60 * 1000L; //60 seconds

 @XmlElement(name = "ssl-config", required = false)
 private SSLConfiguration sslConfiguration;

 @XmlElement(name = "protocol-version")
 private String protocolVersion = "1.1";

 //getters and setters
}
And here is base implementation of WebServicePortPool:
package org.lehvolk.common.ws.pool;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.exceptionhandler.CacheExceptionHandler;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

import org.apache.log4j.Logger;
import org.lehvolk.common.ssl.SSLConfiguration;
import org.lehvolk.common.ssl.SSLUtils;
import org.lehvolk.common.ws.WSConfigurator;
import org.lehvolk.common.ws.WebServicePort;

/**
 * @param <T> service parameterization
 */
public abstract class AbstractWebServicePortPool<T> implements WebServicePortPool<T> {

 private static class WebServiceAddressKey {

  private final String address;
  private final Long stamp;

  /**
   * @param address - web-service address
   */
  public WebServiceAddressKey(String address) {
   this(address, System.nanoTime());
  }

  /**
   * @param address - web-service address
   * @param stamp - last time of port using
   */
  public WebServiceAddressKey(String address, Long stamp) {
   String adr;
   if (address == null || (adr = address.trim()).isEmpty()) {
    throw new IllegalArgumentException("Cannot create WebServicePortPool with null or empty address value '"
      + address + "'");
   }
   this.address = adr;
   this.stamp = stamp;
  }

  /**
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
   if (obj instanceof WebServiceAddressKey) {
    WebServiceAddressKey key = (WebServiceAddressKey) obj;
    boolean result = key.address.equals(address);
    if (result && stamp != null && key.stamp != null) {
     return stamp.equals(key.stamp);
    }
    return result;
   }
   return false;
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
   int result = 17;
   result = result * 31 + address.hashCode();
   return result;
  }

  /**
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
   return String.format("WebServiceAddressKey [%s, %d]", address, stamp);
  }
 }

 protected Logger log = Logger.getLogger(getClass());

 /** WS configurator instance */
 @Inject
 protected WSConfigurator configurator;

 /** WS client configuration */
 protected volatile WSClientConfiguration configuration;

 /** SSLSocket factory instance */
 protected SSLSocketFactory sslFactory = null;

 private Cache cache;

 /**
  * {@inheritDoc}
  */
 @Override
 @SuppressWarnings("unchecked")
 public WebServicePort<T> getPort(String address) {
  WebServiceAddressKey key = new WebServiceAddressKey(address, null);
  synchronized (cache) {
   Element element = cache.get(key);
   if (element != null) {
    cache.remove(element.getObjectKey());
    return (WebServicePort<T>) element.getObjectValue();
   }
  }
  return createPort(address);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 @PostConstruct
 public void postConstruct() {
  configuration = getConfiguration();
  sslFactory = getSSLSocketFactory();
  createCache(configuration.getPoolConfig());
 }

 private void createCache(PortsPoolConfiguration poolConfig) {
  CacheManager manager = CacheManager.getInstance();
  if (manager.cacheExists(poolConfig.getCacheName())) {
   throw new IllegalArgumentException("Cache with name '" + poolConfig.getCacheName() + "' already exists");
  }
  cache = new Cache(
    poolConfig.getCacheName(),
    poolConfig.getPoolSize() == null ? 0 : poolConfig.getPoolSize(),
    MemoryStoreEvictionPolicy.FIFO,
    false,
    null,
    false,
    0, 0,
    false,
    0,
    null);

  manager.addCache(cache);

  // assign exception handler
  cache.setCacheExceptionHandler(new CacheExceptionHandler() {

   @Override
   public void onException(Ehcache cache, Object key, Exception e) {
    String msg = String.format("Exception occurred while operating %s %s", cache.getName(), key == null ? "" : key);
    log.error(msg, e);
   }
  });
 }

 /**
  * Create port instance
  * 
  * @param address - address
  * @return port instance
  */
 protected abstract WebServicePort<T> createPort(String address);

 /**
  * @return configuration for service
  */
 protected abstract WSClientConfiguration getConfiguration();

 /**
  * {@inheritDoc}
  */
 @Override
 public void putPort(WebServicePort<T> port) {
  if (port == null) {
   return;
  }
  WebServiceAddressKey key = new WebServiceAddressKey(port.getAddress());
  cache.put(new Element(key, port));
 }

 /**
  * get port by address from configuration
  * 
  * @return instance of port
  */
 @Override
 public WebServicePort<T> getPort() {
  return getPort(configuration.getWsAddress());
 }

 private SSLSocketFactory getSSLSocketFactory() {
  SSLConfiguration sslConf = configuration.getSslConfiguration();
  if (sslConf != null && sslConf.getEnabled()) {
   try {
    SSLContext ctx = SSLUtils.createSSLContext(configuration.getSslConfiguration());
    return ctx.getSocketFactory();
   } catch (Exception e) {
    throw new IllegalArgumentException("Error ssl initialization", e);
   }
  }
  return null;

 }

 /**
  * {@inheritDoc}
  */
 @Override
 public void reset() {
  configuration = getConfiguration();
  sslFactory = getSSLSocketFactory();
  cache.removeAll();
 }

 /**
  * {@inheritDoc}
  */
 @Override
 public void shutdown() {
  if (cache != null) {
   CacheManager.getInstance().removeCache(configuration.getPoolConfig().getCacheName());
  }
 }
}
In AbstractWebServicePortPool there are two abstract methods: getConfiguration (to provide different ways of getting configuration) and createPort. The last one to provide creation of port instance. Also there is WSConfigurator field with @Inject annotation (if someone doesn't use CDI then setter or special construct should be used in implementation).
An simple implemenation for our Processing service you can see below:
package org.lehvolk.common.ws.example;

import java.io.File;

import javax.inject.Singleton;
import javax.xml.bind.JAXB;

import org.lehvolk.common.ws.WebServicePort;
import org.lehvolk.common.ws.pool.AbstractWebServicePortPool;
import org.lehvolk.common.ws.pool.WSClientConfiguration;

// for ejb singleton use javax.ejb.Singleton instead of javax.inject.Singleton
@Singleton
public class ProcessingWebServicePool extends AbstractWebServicePortPool<ProcessingBean> {

 private ProcessingService service = new ProcessingService();

 /**
  * {@inheritDoc}
  */
 @Override
 protected WebServicePort<ProcessingBean> createPort(String address) {
  ProcessingBean bean = service.getPort();
  WebServicePort<ProcessingBean> port = new WebServicePort<ProcessingBean>(bean, null, address);
  boolean verifyHost = configuration.getSslConfiguration() == null ? false : configuration.getSslConfiguration()
    .getVerifyHost();
  return configurator.configurePort(port, address, configuration.getConnectionTimeout(),
    configuration.getSocketReadTimeout(), sslFactory, verifyHost);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected WSClientConfiguration getConfiguration() {
  return JAXB.unmarshal(new File("META-INF/conf/ws-conf.xml"), WSClientConfiguration.class);
 }
}
If application deployed in environment which supports J2EE6 then should be used EJB singleton annotation and configurae binding for WSConfigurator (for example by @Produces annotation in some factory). If Guice uses for CDI then binding should be written in AbstractModule implementation (and for convenience Guice can be configured to invoke after creation method annotated with @PostConstruct). Below there is code of uses of this pool:
@Stateless
public class SomeBean {

 @Inject 
 private ProcessingWebServicePool pool;

 pubic void call(){
  WebServicePort<ProcessingBean> port = pool.getPort();
  try{
   //logic
  } catch(Exception e) {
   //exception logic
  } finally {
   pool.putPort(port);
  }
 }

}
And now the final part of post - implementations of WSConfiguration for CXF and JAX-WS.
Here is one for CXF:
package org.lehvolk.common.ws.impl;

import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.xml.ws.BindingProvider;

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.ConnectionType;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.log4j.Logger;
import org.lehvolk.common.ws.WSConfigurator;

/**
 * CXF-specific implementation of {@link WSConfigurator}
 */
public class CXFWSConfigurator implements WSConfigurator {

 private Logger log = Logger.getLogger(getClass());

 /**
  * {@inheritDoc}
  */
 @Override
 public <T> T configurePort(T port, String wsAddress, long connTimeout, long readTimeout, SSLSocketFactory sf,
   boolean verifyHost) {
  Map<String, Object> reqCtx = ((BindingProvider) port).getRequestContext();
  reqCtx.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, wsAddress);

  Client client = ClientProxy.getClient(port);
  HTTPConduit http = (HTTPConduit) client.getConduit();

  HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
  httpClientPolicy.setConnectionTimeout(connTimeout);
  httpClientPolicy.setReceiveTimeout(readTimeout);
  httpClientPolicy.setConnection(ConnectionType.CLOSE);

  http.setClient(httpClientPolicy);

  TLSClientParameters tls = new TLSClientParameters();

  if (sf != null) {
   tls.setSSLSocketFactory(sf);
   tls.setDisableCNCheck(!verifyHost);
   http.setTlsClientParameters(tls);
  } else {
   try {
    tls.setSSLSocketFactory(SSLContext.getDefault().getSocketFactory());
    http.setTlsClientParameters(tls);
   } catch (Exception e) {
    log.error("Error of port default SSL configuration applying", e);
    throw new IllegalArgumentException("fail to configure ws client by configuration", e);
   }
  }
  return port;
 }
}

And here is for JAX-WS:
package org.lehvolk.common.ws.impl;

import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.xml.ws.BindingProvider;

import org.apache.log4j.Logger;
import org.lehvolk.common.ws.WSConfigurator;

import com.sun.xml.internal.ws.client.BindingProviderProperties;

/**
 * JAX-WS specific implementation
 */
public class JAXWSConfigurator implements WSConfigurator {

 private Logger log = Logger.getLogger(getClass());

 /**
  * {@inheritDoc}
  */
 @Override
 public <T> T configurePort(T port, String wsAddress, long connTimeout, long readTimeout, SSLSocketFactory sf,
   boolean verifyHost) {

  Map<String, Object> reqCtx = ((BindingProvider) port).getRequestContext();

  reqCtx.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, wsAddress);
  reqCtx.put(BindingProviderProperties.CONNECT_TIMEOUT, connTimeout);
  reqCtx.put(BindingProviderProperties.REQUEST_TIMEOUT, readTimeout);

  // if it possible implement own javax.net.ssl.HostnameVerifier and set it in property
  // reqCtx.put(BindingProviderProperties.HOSTNAME_VERIFIER, new HostnameVerifierImpl());

  if (sf == null) {
   try {
    reqCtx.put(BindingProviderProperties.SSL_SOCKET_FACTORY, SSLContext.getDefault().getSocketFactory());
   } catch (Exception e) {
    log.error("Error of port default SSL configuration applying", e);
    throw new IllegalArgumentException("fail to configure ws client by configuration", e);
   }
  } else {
   reqCtx.put(BindingProviderProperties.SSL_SOCKET_FACTORY, sf);
  }
  return port;
 }
}
Thats all :-)

Saturday, February 25, 2012

Using SSL

In this post I want to to talk about ssl and https particulary.

There are two different strategies to use ssl: simple (when traffic between client and server should only be secured) and custom (when client identify server by server's certificate or server identificate client by client's certificate or both identifies each other). As you can see second strategy is more complicated then first.
In Java logic of ssl communication incapsulated in SSLContext class. Let's construct instance of SSLContext for simple case of ssl. To do this we need implementation of Dummy trust manager:
package org.lehvolk.common.ssl;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

/**
 * Trust manager, makes all hosts trusted
 */
public class DummyTrustManager implements X509TrustManager {

 /**
 * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[],
 *      java.lang.String)
 */
 public void checkClientTrusted(X509Certificate[] chain, String authType)
  throws CertificateException {
 }

 /**
 * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[],
 *      java.lang.String)
 */
 public void checkServerTrusted(X509Certificate[] chain, String authType)
  throws CertificateException {
 }

 /**
 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
 */
 public X509Certificate[] getAcceptedIssuers() {
  return null;
 }
}
And code belove create dummy SSLConext:
 SSLContext sslcontext = SSLContext.getInstance("SSL");
 sslcontext.init(null, new TrustManager[] { new DummyTrustManager() }, null);

Read more Let's consider complicated case of ssl. First let's choose parameters to configure ssl connection. So we need to know is custom ssl enable or not. If custom ssl enable we should know which client certificate used for connection and is server certificate trusted for us. Hence we have following properties:
  1. enabled - if ssl enabled
  2. keyStorePath - path to keystore with client certificate.
  3. keyStorePassword - password to key store.
  4. trustStorePath - trust store path with trusted server certificate.
  5. trustStorePassword - password for trust store.
  6. verifyHost - verify host. it needs to verify that host in certificate and request host equals.
  7. checkHostTrusted - check that server is trusted.
This configuration parameters can be store in simple java pojo object.   

package org.lehvolk.common.ssl;

import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "ssl-configuration")
@XmlAccessorType(XmlAccessType.FIELD)
public class SSLConfiguration {

 @XmlElement(name = "keystore-path", required = false)
 private String keyStorePath;

 @XmlElement(name = "keystore-password", required = false)
 private String keyStorePassword;

 @XmlElement(name = "truststore-path", required = false)
 private String trustStorePath;

 @XmlElement(name = "truststore-password", required = false)
 private String trustStorePassword;

 @XmlElement(name = "enabled", required = false)
 private boolean enabled = false;

 @XmlElement(name = "verify-host", required = false)
 private boolean verifyHost = false;

 @XmlElement(name = "check-host-trusted", required = false)
 private boolean checkHostTrusted = false;
 
 // getters and setters

}
As you can see this configuration can be stored in xml file. So we can write xml file with configuration and use it in out application:

    /path/client.keystore
    password
    /path/server.keystore
    password
    false
    false
    false

Also we need implemetation of dummy host verifier:
package org.lehvolk.common.ssl;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

public class DummyHostVerifier implements HostnameVerifier {

 public boolean verify(String name, SSLSession sess) {
  return true;
 }
}
The following util class has method which create SSLContext from SSLConfiguration.
package org.lehvolk.common.ssl;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Utility class for working with SSL
 */
public class SSLUtils {

 public static final HostnameVerifier dummyHostVerifier = new DummyHostVerifier();
 public static final X509TrustManager dummyTrustManager = new DummyTrustManager();
 /** Internal JAXWS property name, allows setup own implementation of HostnameVerifier */
 public static final String JAXWS_HOSTNAME_VERIFIER = "com.sun.xml.internal.ws.transport.https.client.hostname.verifier";

 /** Internal JAXWS property name, allows setup own implementation of SSLSocketFactory */
 public static final String JAXWS_SSL_SOCKET_FACTORY = "com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory";

 /**
  * Creates {@link SSLContext} according with specified parameters
  * 
  * @param conf - {@link SSLConfiguration}
  * @return initialized SSLSocketFactory
  * @throws SSLException if error while building SSLContext occurred
  */
 public static SSLContext createSSLContext(SSLConfiguration conf) throws SSLException {

  try {
   KeyManager[] keyManagers = null;

   if (conf.getKeyStorePath() != null) {
    KeyStore keyStore = loadKeyStore(conf.getKeyStorePath(), conf.getKeyStorePassword());

    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, conf.getKeyStorePassword().toCharArray());
    keyManagers = keyManagerFactory.getKeyManagers();
   }

   TrustManager[] trustManagers = null;

   if (conf.getCheckHostTrusted()) {
    if (conf.getTrustStorePath() != null) {
     KeyStore trustStore;
     trustStore = loadKeyStore(conf.getTrustStorePath(), conf.getTrustStorePassword());
     TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory
       .getDefaultAlgorithm());
     trustManagerFactory.init(trustStore);
     trustManagers = trustManagerFactory.getTrustManagers();
    }
   } else {
    trustManagers = new TrustManager[] { dummyTrustManager };
   }

   SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");

   SSLContext sslContext = SSLContext.getInstance("SSL");
   sslContext.init(keyManagers, trustManagers, secureRandom);

   return sslContext;
  } catch (Exception e) {
   throw new SSLException("Can't create SSLContext", e);
  }

 }

 /**
  * Creates {@link SSLContext}
  * 
  * @return initialized SSLSocketFactory
  * @throws SSLException if error while building SSLContext occurred
  */
 public static SSLContext createDummySSLContext() throws SSLException {
  try {
   SSLContext sslcontext = SSLContext.getInstance("SSL");
   sslcontext.init(null, new TrustManager[] { dummyTrustManager }, null);
   return sslcontext;
  } catch (Exception e) {
   throw new SSLException("Can't create SSLContext", e);
  }
 }

 /**
  * @return {@link HostnameVerifier} implementation witch allow all hosts for certificate
  */
 public static HostnameVerifier getAllowAllHostsVerifier() {
  return dummyHostVerifier;
 }

 /**
  * load key store
  * 
  * @param keyStorePath path to key store
  * @param keyStorePass key store pass
  * @return KeyStore instance
  * @throws KeyStoreException key store exception
  * @throws NoSuchAlgorithmException if can't read
  * @throws CertificateException if can't read
  * @throws IOException if can't read
  */
 //Initialize KeyStore from specified path and using given password
 public static KeyStore loadKeyStore(String keyStorePath, String keyStorePass) throws KeyStoreException,
   NoSuchAlgorithmException, CertificateException, IOException {
  FileInputStream fis = null;
  try {
   KeyStore keyStore = KeyStore.getInstance("JKS");
   fis = new FileInputStream(keyStorePath);
   keyStore.load(fis, keyStorePass != null ? keyStorePass.toCharArray() : null);
   return keyStore;
  } finally {
   if (fis != null) {
    fis.close();
   }
  }
 }

}
Now let see how to use this. I want to write simple http client (how to use ssl over soap services I will discribe in another post). I choose apache httpcomponents as a framework for http/https implementation. In apache http components there is implementation of thread safe connection manager which pool and reuse http/https connections. It's realy good solution because establishing https connection has big cost, so re-open https connection on every request may affect the performance of application.
To have ability to configure connection manager and ssl setting lets make HttpConfiguration pojo class.
package org.lehvolk.common.http;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.lehvolk.common.ssl.SSLConfiguration;

/**
 * configuration for http client
 */
@XmlRootElement(name = "http-configuration")
@XmlAccessorType(XmlAccessType.FIELD)
public class HttpConfiguration {

 // connection timeout in milliseconds
 @XmlElement(name = "conn-timeout", required = false)
 private Integer connectionTimeout;

 @XmlElement(name = "max-connections")
 private Integer maxConnections;

 @XmlElement(name = "max-connections-per-host", required = false)
 private Integer maxConnectionsPerHost;

 @XmlElement(name = "ssl-configuration", required = false)
 private SSLConfiguration sslConfiguration;

 //getters and setters
}
Now using this configuration (as you can see you can store it in xml file) we can write thread safe http provider.
package org.lehvolk.common.http;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.lehvolk.common.ssl.DummyTrustManager;
import org.lehvolk.common.ssl.SSLConfiguration;
import org.lehvolk.common.ssl.SSLUtils;

/**
 * Http provider
 */
public class HttpProvider {

 private HttpParams httpParameters;
 private ThreadSafeClientConnManager cm;
 private DefaultHttpClient client;

 /**
  * @param configuration configuration
  */
 public HttpProvider(HttpConfiguration configuration) throws SSLException {
  httpParameters = getParams(configuration);
  SchemeRegistry registry = new SchemeRegistry();
  try {
   SSLConfiguration sslConfig = configuration.getSslConfiguration();
   Scheme[] schemes = getSchemes(sslConfig);
   for (Scheme scheme : schemes) {
    registry.register(scheme);
   }

   // creates thread safe manager for http connections
   cm = new ThreadSafeClientConnManager(httpParameters, registry);
   client = new DefaultHttpClient(cm, httpParameters);
  } catch (Exception e) {
   throw new SSLException("Error initializing http provider", e);
  }
 }

 private Scheme[] getSchemes(SSLConfiguration sslConfig) throws SSLException {
  try {
   if (sslConfig == null || !sslConfig.getEnabled()) {
    SSLContext sslcontext = SSLContext.getInstance("SSL");
    sslcontext.init(null, new TrustManager[] { new DummyTrustManager() }, null);
    SSLSocketFactory dummyFactory = new SSLSocketFactory(sslcontext);
    dummyFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    Scheme[] schemes = new Scheme[2];
    schemes[0] = new Scheme("http", new PlainSocketFactory(), 80);
    schemes[1] = new Scheme("https", dummyFactory, 443);
    return schemes;
   }

   SSLContext ctx = SSLUtils.createSSLContext(sslConfig);
   SSLSocketFactory sf = new SSLSocketFactory(ctx);
   sf.setHostnameVerifier(sslConfig.getVerifyHost() ? SSLSocketFactory.STRICT_HOSTNAME_VERIFIER
     : SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

   Scheme[] schemes = new Scheme[1];
   schemes[0] = new Scheme("https", sf, 443);

   return schemes;
  } catch (Exception e) {
   throw new SSLException(e);
  }
 }

 private HttpParams getParams(HttpConfiguration configuration) {
  HttpParams params = new BasicHttpParams();

  Integer maxConnections = configuration.getMaxConnections();
  Integer maxPerHostConnections = configuration.getMaxConnectionsPerHost();
  if (maxPerHostConnections == null) {
   maxPerHostConnections = maxConnections;
  }

  // Increase max total connection
  ConnManagerParams.setMaxTotalConnections(params, maxConnections);
  // Increase connection per route
  ConnPerRouteBean connPerRoute = new ConnPerRouteBean(maxPerHostConnections);
  ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);

  ConnManagerParams.setTimeout(params, configuration.getConnectionTimeout());
  return params;
 }

 /**
  * {@inheritDoc}
  */
 public HttpResponse executeRequest(HttpUriRequest request) throws ClientProtocolException, IOException {
  return client.execute(request);
 }

 /**
  * {@inheritDoc}
  */
 public void safelyShutdown() {
  new Thread() {

   @Override
   public void run() {
    if (cm != null) {
     while (cm.getConnectionsInPool() > 0) {
      cm.closeExpiredConnections();
      cm.closeIdleConnections(10, TimeUnit.MILLISECONDS);

      try {
       sleep(2000);
      } catch (InterruptedException e) {
       // ignore
      }
     }
     cm.shutdown();
    }
   };
  }.start();
 }

 /**
  * {@inheritDoc}
  */
 public void shutdown() {
  if (cm != null) {
   cm.shutdown();
  }
 }
}

Whats happend in this class. If we don't have the ssl confguration or it's not enabled then HttpProvider makes requests under http or simple https (without customized certificates). If we need to call some https service with specified certificate we should specify enabled property of SSLConfiguration in HttpConfiguration. In this situation provider make calls only under https with specified ssl configuration. Also we use verifyHost property of SSLConfiguration when point which host verifier use in SSLSocketFactory instance.
When provider instance doesn't needed anymore then shutdown or safetyShoutdown method should be called. I should notice that create instance of provider for single call is very expensive. Best practice is to create instance of HttpProvider and cached them into classic signleton or ejb sigleton bean. Also don't forget to clean up entity in HttpResponse.