Friday, 19 August 2011

Java SSL Whitelisting of Peer Certificate Subject Names

Helping a colleague just today has reminded me to finish this post drafted a long time ago. Last year I was dipping into the Java SSL libraries to write a short piece of code to make a call to a service running over HTTPS where mutual authentication is required. - The client authenticates the server based on the server's X.509 certificate passed in the SSL handshake but in addition, the client must pass a certificate to enable the server to authenticate it the client.

By default, the Java SSL trust manager will trust peer certificates provided that they are issued by any of the CAs (Certificate Authorities) whose certificates appear in the default trust store for the JVM.   It's possible to customise the trust manager to use a given trust store to give more fine grained control but what if we want to trust only a certain subset of certificates issued by a given CA or CAs?  

One way to achieve this is to whitelist based on the peer certificate DN or Distinguished Name.   This is something that is straightforward to do on the server side with, for example, Apache using the SSLRequire directive. It's also a practice used in Grid computing authorisation middleware with ACLs (Access Control Lists).  Rather than the protection of some server-side resource, the problem to solve in this case is a client invocation.

Returning to the SSL API then, this can be achieved by implementing interface. The key method for client side checking of server certificates is checkServerTrusted. The relevant hooks can be set in here to check the peer certificate against a whitelist:

    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        // Default trust manager may throw a certificate exception
        pkixTrustManager.checkServerTrusted(chain, authType);
        // If chain is OK following previous check, then execute whitelisting 
        // of DN
        X500Principal peerCertDN = null;
        if (certificateDnWhiteList == null || 
        int basicConstraints = -1;
        for (X509Certificate cert : chain) {
            // Check for CA certificate first - ignore if this is the case
            basicConstraints = cert.getBasicConstraints();
            if (basicConstraints > -1)

            peerCertDN = cert.getSubjectX500Principal();
            for (X500Principal dn : certificateDnWhiteList)
                if (peerCertDN.getName().equals(dn.getName()))

            throw new CertificateException("No match for peer certificate \"" + 
  peerCertDN + "\" against Certificate DN whitelist");

pkixTrustManager is the default trust manager whilst certificateDnWhiteList is a list of accepted DNs as X500Principal types.  These can be initialised in the classes' constructor from a properties file or some other input.  The pkixTrustManager.checkServerTrusted call applies the default verification of the peer's certificate based on the CA certificates present in the client's trust store. If this succeeds, a loop then iterates over the certificate chain returned by the peer skipping any CA certificates*.  Once the peer certificate is found, its DN is extracted and checked against the whitelist. If matched, it returns silently to the caller indicating all is OK. If no match is found, a CertificateException is thrown to indicate that the peer certificate is not in the accepted list of DNs.  This could easily be extended to do more sophisticated matching for example using regular expressions.

This technical article provides some more background (scroll down a long way to the Trust Manager heading).  The full source for the example above is available here.

[* The peer can of course pass back not only its own certificate, but any intermediate CA certificates needed to complete the chain of trust to a root CA certificate held by the client.]