How to set up Apache CXF client to use WebSphere truststore? (Receiving "No trusted certificate found" exception.)
Asked Answered
C

4

10

First, I'll start with a summary. I'm using an Apache CXF client to communicate over SSL with an Apache CXF service provider that is using a self-signed certificate. I imported the certificate into the WebSphere truststore on the client server, but I still receive a "javax.net.ssl.SSLHandshakeException: SSLHandshakeException invoking https://somesvcprovider.com/appname/svc/myservice: com.ibm.jsse2.util.h: No trusted certificate found" exception.

Now, here's the details:

I have an Apache CXF web service client that I am configuring using Spring, and the client is deployed to a WebSphere 6.1 application server. The CXF client communicates with an Apache CXF service provider on a different WebSphere server. The communication uses SSL.

The service provider is using a self-signed certificate. I've imported the provider's certificate into the WebSphere truststore on the client server through the administrative console. I accomplished this by going to SSL certificate and key management > SSL configurations > NodeDefaultSSLSettings > Key stores and certificates > NodeDefaultTrustStore > Signer certificates; then I used the "Retrieve from port" tool to import the certificate.

However, I still receive this error when attempting to contact the service provider: "javax.net.ssl.SSLHandshakeException: SSLHandshakeException invoking https://somesvcprovider.com/appname/svc/myservice: com.ibm.jsse2.util.h: No trusted certificate found".

The Spring configuration file is as follows:

<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://cxf.apache.org/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/jaxws
      http://cxf.apache.org/schemas/jaxws.xsd
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">
    <http:conduit name="*.http-conduit">
        <!-- deactivate HTTPS url hostname verification (localhost, etc) -->
        <!-- WARNING ! disableCNcheck=true should not used in production. -->
        <http:tlsClientParameters disableCNCheck="true" />
    </http:conduit>
    <!-- Read properties from property file(s). -->
    <bean id="propertyPlaceholderConfigurer"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <!-- The *.spring.properties files are prefixed with a system property
                    that is set on the WebSphere server. -->
                <value>classpath:spring.${my.env}.properties</value>
            </list>
        </property>
    </bean>
    <jaxws:client id="myServiceClient"
        serviceClass="com.client.stub.cxf.IMyService"
        address="${my.svc.url}" />
    <bean id="myReport" class="com.client.MyReportRequestor">
        <property name="client" ref="myServiceClient"/>
    </bean>
</beans>

As shown above, the CXF client is injected via a setter by Spring. The code to contact the service is below:

List<String> formNames = client.retrieveNames(formIdsList);

Also, I don't know if this is related, but no trust managers are returned when I inspect the TLSClientParameters object on the CXF client at runtime. The code to do the inspection is below:

// Get the trust managers for this client.
Client proxy = ClientProxy.getClient(client);
HTTPConduit conduit = (HTTPConduit) proxy.getConduit();
TLSClientParameters tls = conduit.getTlsClientParameters();
TrustManager[] trustManagers = tls.getTrustManagers();  // trustManagers is null

Is there anything else that I need to do to get the Apache CXF client to trust the self-signed certificate?

I prefer to not have to specify the path to a truststore along with a password in the configuration file.

Thank you!

Calcutta answered 1/9, 2011 at 18:55 Comment(0)
T
1

I don't think you can use the WAS keystores just like that with external component (Apache CXF). You must probably build and use your own TrustManager. There seem to be several working examples around for that.

Tomkin answered 18/9, 2011 at 8:42 Comment(2)
I can't find a way to use WAS truststores, either, so I'm going to assume that you have to specify the truststore in the configuration file as you mentioned.Calcutta
I find the @kjetil solution more feasible, as it doesn't require custom developmentFay
H
8

CXF is probably using the wrong SSL socket factory.

Try adding this to your Spring config:

<http-conf:conduit name="*.http-conduit">
    <http-conf:tlsClientParameters useHttpsURLConnectionDefaultSslSocketFactory="true"/>
</http-conf:conduit>
Habitude answered 13/6, 2012 at 7:8 Comment(1)
Works for me too... even when using ProxyFactoryBean to create client dinamically [WAS 8.5] [CXF 3.1.8]Fay
R
2

Looking at how CXF and WAS work, it is fairly straightforward to access the Websphere's SSLSocketFactory and pass it to CXF using an outbound interceptor.

If you use the following class:

public class WebsphereSslOutInterceptor extends AbstractPhaseInterceptor<Message> {

  private String sslAlias = null;

  public WebsphereSslOutInterceptor() {
    super(Phase.SETUP);
  }

  public void handleMessage(Message message) throws Fault {
    Conduit conduit = message.getExchange().getConduit(message);
    if (conduit instanceof HTTPConduit) {
      HTTPConduit httpConduit = (HTTPConduit)conduit;
      String endpoint = (String) message.get(Message.ENDPOINT_ADDRESS);
      if (endpoint != null) {
        try {
          URL endpointUrl = new URL(endpoint);
          Map<String, String> connectionInfo = new HashMap<String, String>();

          connectionInfo.put(
            JSSEHelper.CONNECTION_INFO_REMOTE_HOST, 
            endpointUrl.getHost());
          connectionInfo.put(
            JSSEHelper.CONNECTION_INFO_REMOTE_PORT, 
            Integer.toString(endpointUrl.getPort()));
          connectionInfo.put(
            JSSEHelper.CONNECTION_INFO_DIRECTION, 
            JSSEHelper.DIRECTION_OUTBOUND);
          SSLSocketFactory factory = 
            JSSEHelper.getInstance().getSSLSocketFactory(
              sslAlias, 
              connectionInfo, 
              null);

          TLSClientParameters tlsClientParameters = httpConduit.getTlsClientParameters();
          if (tlsClientParameters != null) {
            tlsClientParameters.setSSLSocketFactory(factory);
          }
        } catch (MalformedURLException e) {
          throw new Fault(e);
        } catch (SSLException e) {
          throw new Fault(e);
        }
      }
    }
  }

  public void setSslAlias(String sslAlias) {
    this.sslAlias = sslAlias;
  }
}

Then you'll be able to hook up to Websphere's SSLSocketFactory and can optionally use the "Dynamic Outbound Endpoint SSL Configuration" settings to specify any client certs, by specifying the interceptor in the jaxws:client tag:

  <jaxws:client id="proxyName"
        serviceClass="proxyClass"
        address="${web.service.endpointaddress}">

    <jaxws:outInterceptors>
        <bean class="my.pkg.WebsphereSslOutInterceptor" />
    </jaxws:outInterceptors>
</jaxws:client>

As an aside, if the sslAlias property is declared in the WebsphereSslOutInterceptor, a client certificate can be chosen based on its alias.

Because this is using the SSLSocketFactory from Websphere, the trust stores will also be used from Websphere.

EDIT:

I used CXF 2.3.6 and Websphere 6.1

Roselani answered 23/11, 2011 at 12:13 Comment(2)
This solution works for me as long as I keep the interceptor definition in endpoint configuration. However, if I put it on the bus, the handleMessage method doesn't get called. Does anybody know why that is? I'd rather not have to specify the same interceptor for every endpoint.Anatomize
@Roselani If I am using Websphere Dynamic Endpoints, do I still need all the Interceptor and the SSLSocketFactory stuff? My understanding is that I can make a simple SSL call from my code and the Dynamic Endpoint should intercept all outgoing requests based on URL matching. Is that correct?Flickertail
M
2

beny23's solution works great for me on WAS7, with the following modifications (reason: httpConduit.getTlsClientParameters() might be null):

Replace this part:

    TLSClientParameters tlsClientParameters = httpConduit.getTlsClientParameters();
    if (tlsClientParameters != null) {
      tlsClientParameters.setSSLSocketFactory(factory);
    }

With this:

    TLSClientParameters tlsClientParameters = httpConduit.getTlsClientParameters();
    if (tlsClientParameters == null) {
      tlsClientParameters = new TLSClientParameters();
      httpConduit.setTlsClientParameters(tlsClientParameters);
    }
    tlsClientParameters.setSSLSocketFactory(factory);
Mylohyoid answered 23/1, 2013 at 10:15 Comment(0)
T
1

I don't think you can use the WAS keystores just like that with external component (Apache CXF). You must probably build and use your own TrustManager. There seem to be several working examples around for that.

Tomkin answered 18/9, 2011 at 8:42 Comment(2)
I can't find a way to use WAS truststores, either, so I'm going to assume that you have to specify the truststore in the configuration file as you mentioned.Calcutta
I find the @kjetil solution more feasible, as it doesn't require custom developmentFay

© 2022 - 2024 — McMap. All rights reserved.