29 August 2016

Custom JSSE Truststore to enable XKMS Certificate Validation

Recently I was involved in a project which uses a central XKMS Server for certificate and trust management. This was all working fine within the Talend runtime with a custom wss4j crypto provider. However the need raised to perform client certificate validations (mutal SSL) with Apache Fediz running inside an Apache Tomcat server.


Usually I would use a JKS truststore for Tomcat to add trusted certificates (CAs). However this was not possible for this project, because all certificates will be managed inside an LDAP accessible via a XKMS service. Searching for a solution to extend Tomcat to support XKMS based certificate validation I came across the JSSE Standard.

Reading throw the documentation was not so straightforward and clear. But searching through the internet finally helped me to achieve my goal. In this blog post, I'll show you what I had to do, to enabled XKMS based SSL certificate validation in Tomcat.
To manage your SSL truststore settings you can use standard System or Tomcat properties:

System Properties Tomcat Properties Purpose
javax.net.ssl.trustStore truststoreFile System location for JKS truststore file
javax.net.ssl.trustStorePassword truststorePass Password for JKS truststore
javax.net.ssl.trustStoreProvider truststoreProvider Provider for a truststoreFactory implementation
javax.net.ssl.trustStoreType truststoreType Type of your truststore. Default is "JKS"
n/a trustManagerClassName Custom trust manager class to use to validate client certificates

Settings are considered in the following order:
  1. Tomcat truststore properties
  2. System Properties
  3. Tomcat keystore properties
  4. Default Values
If a trustManagerClassName is set, this implementation will be used and all other truststore settings will be ignored. If a truststore provider is defined any Java standard provider will be ignored.

You can review this behavior in the Tomcat JSSESocketFactory init method.

The easiest way to achieve my goal was to implement my own XKMSTrustManager implementing the javax.net.ssl.X509TrustManager interface.
public class XKMSTrustManager implements X509TrustManager {

    private static final Logger LOG = LoggerFactory.getLogger(XKMSTrustManager.class);

    private XKMSInvoker xkms;

    public XKMSTrustManager() throws MalformedURLException {
        XKMSService xkmsService = new XKMSService(
            URI.create(System.getProperty("xkms.wsdl.location", "http://localhost:8040/services/XKMS/?wsdl"))
            .toURL());
        xkms = new XKMSInvoker(xkmsService.getXKMSPort());
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        LOG.debug("Check client trust for: {}", chain);
        validateTrust(chain);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        LOG.debug("Check server trust for: {}", chain);
        validateTrust(chain);
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[] {};
    }

    protected void validateTrust(X509Certificate[] chain) throws CertificateException {
        if (chain == null) {
            throw new CertificateException("Certificate chain is null");
        }

        if (!xkms.validateCertificate(chain)) {
            LOG.error("Certificate chain is not trusted: {}", chain);
            throw new CertificateException("Certificate chain is not trusted");
        }
    }
}
Tomcat\conf\server.xml
<Server port="9005" shutdown="SHUTDOWN">

  <Service name="Catalina">

    <Connector port="9443" protocol="org.apache.coyote.http11.Http11Protocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               keystoreFile="idp-ssl-key.jks"
               keystorePass="tompass"
               trustManagerClassName="org.talend.ws.security.jsse.XKMSTrustManager"
               clientAuth="true"
               sslProtocol="TLS" />

  </Service>

</Server>


However setting a trustManager is only possible if this option is provided by your application or if you have access to the source code of the SSL SocketFactory. In all other cases you will have to implement your own Security Provider providing your own truststore factory. This task is much more challenging. During my internet research for this topic I found several pages, which should be a good reference for you, if you have to go this way:

JCA Reference Guide - Crypto Provider

Howto Implement a JCA Provider

JSSE Reference Guide - Customized Certificate Storage

Custom CA Truststore in addition to System CA Truststore

HowTo Register global security provider


<java-home>/lib/security/java.security
security.provider.2=sun.security.provider.Sun
security.provider.3=sun.security.rsa.SunRsaSign
security.provider.4=sun.security.provider.SunJCE 
Advantage: Multiple providers. Adding just the "missing piece".
Disadvantage: System wide configuration

Override security provider settings with system properties

Changing Security Settings via Code

Security.insertProviderAt(new FooBarProvider(), 1);

Register a TrustManager

put("TrustManagerFactory.SunX509", "sun.security.ssl.TrustManagerFactoryImpl$SimpleFactory");
put("TrustManagerFactory.PKIX", "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory");
Using a Custom Certificate Trust Store
Sun JSSE Provider Implementation

5 comments: