Using a pinned certificate for SSL communication

Using a pinned certificate for SSL communication can enhance security when communicating to a known service. The certificate from the server can be hard coded into a client application and used during SSL verification as an additional check. This has the drawback of requiring a client application update prior to the server certificate expiration.

The following example uses Apache HTTPClient and overrides the default TrustStrategy. There is no specific reason for using Apache HTTPClient in this example so feel free to use your favorite client.

This example will use the certificate method of pinning, but be aware there are other techniques such as public key pinning.

https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning

First we will load the certificate file from the classpath. The project will need the certificate file packaged into the .jar file and available in the classpath. For this example, server.cert was obtained from the server and saved into a maven based project under src/main/resources

ClassLoader classLoader = getClass().getClassLoader();
InputStream is = classLoader.getResourceAsStream("server.cert");				
CertificateFactory x509CertFact = CertificateFactory.getInstance("X.509");
X509Certificate pinnedCert = (X509Certificate)x509CertFact.generateCertificate(is);

Next we use a custom TrustStrategy as a verification example. This will not perform SSL validation and is intended as an example only. Do not copy and paste this code into a production application without adding validation.

The code will generate a thumbprint of both the SSL cert found on the server, and the local cert embedded into the jar file. These thumbprints are compared and accepted if they match.

TrustStrategy trustStrategy = new TrustStrategy(){
    @Override
    public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        if (chain.length != 1)
            return false;
        X509Certificate cert = chain[0];

        try {
            String serverThumbprint = getThumbPrint(cert);
            String pinnedThumbprint = getThumbPrint(pinnedCert);
            if (!serverThumbprint.equals(pinnedThumbprint))
                return false;
            } catch (NoSuchAlgorithmException ex) {
                throw new CertificateException("Could not get SHA-256 message digest.", ex);
            }
				
        return true;
    }
};
SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(null, trustStrategy)
    .build();
					
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
    .setSSLSocketFactory(csf)
    .disableCookieManagement()
    .build();

// connect to some https url		
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(jsonRequest));
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
int status = httpResponse.getStatusLine().getStatusCode();
	private String getThumbPrint(X509Certificate cert) 
        throws NoSuchAlgorithmException, CertificateEncodingException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] der = cert.getEncoded();
        md.update(der);
        byte[] bytes = md.digest();
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', 
                '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        StringBuffer buf = new StringBuffer(bytes.length * 2);

        for (int i = 0; i < bytes.length; ++i) {
            buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
            buf.append(hexDigits[bytes[i] & 0x0f]);
        }

        return buf.toString();
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.