It has been a wonderful week with my ramblings with CXF and SSL. But, it all ended up in good mood. Sometimes, it is so hard to find how to use certain features with CXF. I am a great fan of CXF because of its simplicity and intuitive feature set. It provides an array of integration points with best of breed technologies, but sadly most of the crux is buried under its system tests or CXF user mailing list archive. The documentation front is really lacking its meat from the “source”. So, people end up debugging CXF which is the only weapon to invade your configuration issues.

The community around CXF is very healthy and gained substantial momentum in all fronts for the past year or so. CXF user mailing list is such an awful resource. Its the first resort to get your queries answered and if you don’t get a response, don’t wait, just debug yourself. The resources are very limited and their support is really appreciated.

I wanted to contribute a sample for programmatic setup of SSL with CXF using embedded Jetty, so people get it right at the first time. So, I ported the sample “wsdl_first_https” which is bundled with CXF 2.1.1 distribution to a “java_first_https” scenario. The “wsdl_first_https” sample uses Spring based configuration for SSL. Though this scenario seem to fit well where the application uses spring as its underlying DI framework. For people who wish not to care about Spring, why would they want to unnecessarily bundle Spring jars for achieving CXF/SSL support in their application. But, this all depends on what approach you chose to build your services with CXF (either using Spring config or Java APIs). This is just a simple case for building this demo in “no-Spring” way. It opens a whole world of possibilities for dynamically configuring security in your embedded Jetty container.

Lets take the simple Greeter service which greets the client with its name sent as an input parameter.

@WebService
public interface Greeter {

    String greetMe(String me);
}


@javax.jws.WebService(name = "Greeter", serviceName = "SOAPService",
                      targetNamespace = "http://apache.org/hello_world_soap_http")
public class GreeterImpl implements Greeter {
    private static final Logger LOG =
        Logger.getLogger(GreeterImpl.class.getPackage().getName());

    public String greetMe(String me) {
        LOG.info("Executing operation greetMe");
        System.out.println("Executing operation greetMe");
        System.out.println("Message received: " + me + "\n");
        return "Hello " + me;
    }
}

The demo uses the same client and server certificate stores shipped with “wsdl_first_https” demo in CXF 2.1.1. So, don’t forget to include them when running this demo.

The Server side SSL configuration using CXF Java APIs is shown below.

public class Server {

    protected Server() throws Exception {
        System.out.println("Starting Server");
        String address = "https://localhost:9001/SoapContext/SoapPort";
        JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
        sf.setServiceClass(Greeter.class);
        sf.setAddress(address);

        Greeter implementor = new GreeterImpl();
        sf.getServiceFactory().setInvoker(new BeanInvoker(implementor));

        sf = configureSSLOnTheServer(sf, 9001);
        org.apache.cxf.endpoint.Server server = sf.create();
        String endpoint = server.getEndpoint().getEndpointInfo().getAddress();
        
        System.out.println("Server started at " + endpoint);
    }

    private JaxWsServerFactoryBean configureSSLOnTheServer(JaxWsServerFactoryBean sf, int port) {
        try {
            TLSServerParameters tlsParams = new TLSServerParameters();
            KeyStore keyStore = KeyStore.getInstance("JKS");
            String password = "password";
            File truststore = new File("certs\\cherry.jks");
            keyStore.load(new FileInputStream(truststore), password.toCharArray());
            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, password.toCharArray());
            KeyManager[] km = keyFactory.getKeyManagers();
            tlsParams.setKeyManagers(km);

            truststore = new File("certs\\truststore.jks");
            keyStore.load(new FileInputStream(truststore), password.toCharArray());
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);
            TrustManager[] tm = trustFactory.getTrustManagers();
            tlsParams.setTrustManagers(tm);
            FiltersType filter = new FiltersType();
            filter.getInclude().add(".*_EXPORT_.*");
            filter.getInclude().add(".*_EXPORT1024_.*");
            filter.getInclude().add(".*_WITH_DES_.*");
            filter.getInclude().add(".*_WITH_NULL_.*");
            filter.getExclude().add(".*_DH_anon_.*");
            tlsParams.setCipherSuitesFilter(filter);
            ClientAuthentication ca = new ClientAuthentication();
            ca.setRequired(true);
            ca.setWant(true);
            tlsParams.setClientAuthentication(ca);
            JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
            factory.setTLSServerParametersForPort(port, tlsParams);
        } catch (KeyStoreException kse) {
            System.out.println("Security configuration failed with the following: " + kse.getCause());
        } catch (NoSuchAlgorithmException nsa) {
            System.out.println("Security configuration failed with the following: " + nsa.getCause());
        } catch (FileNotFoundException fnfe) {
            System.out.println("Security configuration failed with the following: " + fnfe.getCause());
        } catch (UnrecoverableKeyException uke) {
            System.out.println("Security configuration failed with the following: " + uke.getCause());
        } catch (CertificateException ce) {
            System.out.println("Security configuration failed with the following: " + ce.getCause());
        } catch (GeneralSecurityException gse) {
            System.out.println("Security configuration failed with the following: " + gse.getCause());
        } catch (IOException ioe) {
            System.out.println("Security configuration failed with the following: " + ioe.getCause());
        }

        return sf;
    }

    public static void main(String args[]) throws Exception {
        System.out.println("The server's security configuration will be done programatically.");
        System.out.println();
        new Server();
        System.out.println("Server ready...");

        Thread.sleep(5 * 60 * 1000);
        System.out.println("Server exiting");
        System.exit(0);
    }
}

The SSL server configuration using Java APIs exactly maps to spring config “CherryServer.cxf” used in the distribution.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ** This file configures the Cherry Server.
 -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
  xsi:schemaLocation="
       http://cxf.apache.org/configuration/security  		      http://cxf.apache.org/schemas/configuration/security.xsd

http://cxf.apache.org/transports/http/configuration


http://cxf.apache.org/schemas/configuration/http-conf.xsd


http://cxf.apache.org/transports/http-jetty/configuration


http://cxf.apache.org/schemas/configuration/http-jetty.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <http:destination name="{http://apache.org/hello_world_soap_http}GreeterPort.http-destination"> 
  </http:destination>

  <httpj:engine-factory>
   <httpj:engine port="9001">
    <httpj:tlsServerParameters>
      <sec:keyManagers keyPassword="password">
           <sec:keyStore type="JKS" password="password" 
                file="certs/cherry.jks"/>
      </sec:keyManagers>
      <sec:trustManagers>
          <sec:keyStore type="JKS" password="password"
               file="certs/truststore.jks"/>
      </sec:trustManagers>
      <sec:cipherSuitesFilter>
        <!-- these filters ensure that a ciphersuite with
          export-suitable or null encryption is used,
          but exclude anonymous Diffie-Hellman key change as
          this is vulnerable to man-in-the-middle attacks -->
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
      <sec:clientAuthentication want="true" required="true"/>
    </httpj:tlsServerParameters>
   </httpj:engine>
  </httpj:engine-factory>
</beans>

The Client side SSL configuration using CXF Java APIs is shown below.

public class Client {

    private static void configureSSLOnTheClient(Object c) {
        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(c);
        HTTPConduit httpConduit = (HTTPConduit) client.getConduit();

        try {
            TLSClientParameters tlsParams = new TLSClientParameters();
            tlsParams.setDisableCNCheck(true);

            KeyStore keyStore = KeyStore.getInstance("JKS");
            String trustpass = "password";

            File truststore = new File("certs\\truststore.jks");
            keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);
            TrustManager[] tm = trustFactory.getTrustManagers();
            tlsParams.setTrustManagers(tm);

            truststore = new File("certs\\wibble.jks");
            keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, trustpass.toCharArray());
            KeyManager[] km = keyFactory.getKeyManagers();
            tlsParams.setKeyManagers(km);

            FiltersType filter = new FiltersType();
            filter.getInclude().add(".*_EXPORT_.*");
            filter.getInclude().add(".*_EXPORT1024_.*");
            filter.getInclude().add(".*_WITH_DES_.*");
            filter.getInclude().add(".*_WITH_NULL_.*");
            filter.getExclude().add(".*_DH_anon_.*");
            tlsParams.setCipherSuitesFilter(filter);

            httpConduit.setTlsClientParameters(tlsParams);
        } catch (KeyStoreException kse) {
            System.out.println("Security configuration failed with the following: " + kse.getCause());
        } catch (NoSuchAlgorithmException nsa) {
            System.out.println("Security configuration failed with the following: " + nsa.getCause());
        } catch (FileNotFoundException fnfe) {
            System.out.println("Security configuration failed with the following: " + fnfe.getCause());
        } catch (UnrecoverableKeyException uke) {
            System.out.println("Security configuration failed with the following: " + uke.getCause());
        } catch (CertificateException ce) {
            System.out.println("Security configuration failed with the following: " + ce.getCause());
        } catch (GeneralSecurityException gse) {
            System.out.println("Security configuration failed with the following: " + gse.getCause());
        } catch (IOException ioe) {
            System.out.println("Security configuration failed with the following: " + ioe.getCause());
        }
    }

    public static void main(String args[]) {
        System.out.println("The client's security configuration will be done programatically.");
        System.out.println();
        String address = "https://localhost:9001/SoapContext/SoapPort";
        JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
        proxyFactory.setServiceClass(Greeter.class);
        proxyFactory.setAddress(address);

        Greeter client = (Greeter) proxyFactory.create();
        configureSSLOnTheClient(client);

        System.out.println("Invoking greetMe...");
        try {
            String resp = client.greetMe(System.getProperty("user.name"));
            System.out.println("Server responded with: " + resp);
            System.out.println();

        } catch (Exception e) {
            System.out.println("Invocation failed with the following: " + e.getCause());
            System.out.println();
        }

    }
}

The SSL client configuration using Java APIs exactly maps to spring config “WibbleClient.cxf” used in the distribution.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ** This file configures the Wibble Client
  -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
  xsi:schemaLocation="

http://cxf.apache.org/configuration/security


http://cxf.apache.org/schemas/configuration/security.xsd


http://cxf.apache.org/transports/http/configuration


http://cxf.apache.org/schemas/configuration/http-conf.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans.xsd">

  <http:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit">
    <http:tlsClientParameters disableCNCheck="true">
      <sec:trustManagers>
          <sec:keyStore type="JKS" password="password"
               file="certs/truststore.jks"/>
      </sec:trustManagers>
      <sec:keyManagers keyPassword="password">
           <sec:keyStore type="JKS" password="password" 
                file="certs/wibble.jks"/>
      </sec:keyManagers>
      <sec:cipherSuitesFilter>
        <!-- these filters ensure that a ciphersuite with
          export-suitable or null encryption is used,
          but exclude anonymous Diffie-Hellman key change as
          this is vulnerable to man-in-the-middle attacks -->
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
   </http:conduit>
</beans> 

You can download the NetBeans project here.

Possibly Related Posts:


16 Responses to “Programming SSL for Jetty based CXF services”

  • gel

    Hi,

    I am getting the following error when attempting to access the wsdl – https://localhost:9001/SoapContext/SoapPort?wsdl

    please help. thanks!

    WARNING: EXCEPTION
    javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:742)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1030)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1057)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1041)
    at org.mortbay.jetty.security.SslSocketConnector$SslConnection.run(SslSocketConnector.java:615)
    at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:450)
    Caused by: java.io.EOFException: SSL peer shut down incorrectly
    at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:333)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:723)
    … 5 more
    Oct 3, 2008 4:24:29 PM sun.reflect.NativeMethodAccessorImpl invoke0
    WARNING: EXCEPTION
    javax.net.ssl.SSLProtocolException: handshake alert: no_certificate
    at com.sun.net.ssl.internal.ssl.ServerHandshaker.handshakeAlert(ServerHandshaker.java:1031)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1577)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:866)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1030)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1057)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1041)
    at org.mortbay.jetty.security.SslSocketConnector$SslConnection.run(SslSocketConnector.java:615)
    at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:450)

  • admin

    Try importing the certificates into your browser and then try accessing these SSL enabled web service endpoints.

    -Arul

  • nocturne

    I run all your code but I still get the port mismatch error code.

    in this code

    JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
    factory.setTLSServerParametersForPort(port, tlsParams);

    is the factory is doing something to JaxWsServerFactoryBean sf input in configureSSLOnTheServer method

    regards,
    is there something to do with Ser

  • Arul

    The certificate locations were hard coded in the Server and Client code attached in this blog entry. Make sure you change it to your CXF sample certificates location (For ex: C:\CXF\apache-cxf-2.1.1\samples\wsdl_first_https\certs on windows).

    Let me know if you are still facing this issue.

    -Arul

  • Ashish

    Arul,

    Can you also tell us, how to generate the certificates used with this article

  • paulbrickell

    If I try to access the url with firefox I get an error page with…

    Peer’s certificate has an invalid signature.

    Error code: sec_error_bad_signature

    On the server I get…

    WARNING: EXCEPTION
    javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:117)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1650)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:925)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1089)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1116)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1100)
    at org.mortbay.jetty.security.SslSocketConnector$SslConnection.run(SslSocketConnector.java:615)
    at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:450)

    Any ideas?

    BTW thanks for the blog.

  • paulbrickell

    Ref my own post at May 7th, 2009 at 7:37 am.

    I got this error even after importing all the certificates explicitly.

    But After a restart of firefox I got a warning about low grade security, clicked OK and the page did load.

  • Armin

    Thanks for the useful information, I haven’t run the sample but I needed some info on this subject and now I have it.

  • Victor

    Hi, thanks for the solution.

    I had the same problem as gel.

    javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

    i hosted my server for a client to call, no browser involve. any idea what is wrong?

    thanks in advance :)

  • siva naresh

    nice info out here.
    I have a problem here. I donot know how the webservice is hosted using ssl.

    so the client is not wary of the certificates that it would recieve from the server.

    How does it handle here?

    should it download the certificate and import it to its trusted certificate list ?