Public Key/Certificates pinning in Java – certificates from Let’s Encrypt

Recently our team uses Let’sEncrypt certificate for our backend server for HTTPS communication. Everything goes well since modern browsers all trust “Let’s Encrypt”‘s certificates. The problem only comes when a Java¬†application that we have delivered to our users keeps asking users that our certificate is not trusted and if the users want to add it to trusted certificates. This is a big red flag to any users including security experts to newbies. In the end, it turns out that Let’sEncrypt certificates are not trusted by default by JVM (at least the newest version of JDK at the time I am writing this blog. So our solution is pin our server certificate (Let’s Encrypt Authority) inside our application. In this blog, I am going to share my experience with certificate pinning in Java.

What is certificate/public key pinning?

Pinning is one way to associate a host with a given (expected) public key or certificate. In case of browsers, they have a set of trusted Certificate Authority (CA). When you visit a website that has HTTPS, your browser will check if the certificate presented by that website (let’s say secure-coding.com) is signed by a trusted CA or not. If the certificate is signed by a trusted CA, your browser will trust it and your connection is considered secure (data is transmitted under proper encryption). The whole SSL process is not that simple but I try to keep it as simple as possible.
When you pin a certificate or public key it means that you don’t rely on the default TrustStore any more. You are using your own TrustStore

Why do we pin public key/certificate?

  • What happens if a CA is compromised? Since you have already trusted this CA, it means that certificates signed by this CA will also be trusted even if it is now signed by an attacker who has taken control of the CA. This is the case we may want to avoid by pinning server’s certificate or public key.
  • What if you have a self-signed certificate that is not trusted by common browsers, platforms and your server is set up for communication with your client applications. This is where you can take advantage of public key/certificate pinning.

How to pin public key/certificate in Java?

The code snippet below is referred from Android official website. Basically, we have 6 steps

1. Load CA certificate from an input stream

2. Create your own KeyStore

3. Add (pin) the certificate into your KeyStore

4. Create TrustManagerFactory from the aforementioned KeyStore

5. Load SSLContext from the TrustManagerFactory in step 4

6. Set SSLSocketFactory for your HttpsURLConnection from SSLContext in step 6. 7. Everything is done your app is now able to communicate with the server without having to rely on the built-in KeyStore of any Platforms (e.g JVM)



//1. Load CAs from an InputStream (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");

// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
 ca = cf.generateCertificate(caInput);
 System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
 caInput.close();
}

//2 Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
//3 Add the certificate into our KeyStore
keyStore.setCertificateEntry("ca", ca);

//4 Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

//5 Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

//6 Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
 (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());

 

Disadvantages of Public key/certificate pinning?

When you pin a certificate/public key in your code, it means that if the public key/certificate has been updated, changed, or expired you need to update your source code as well. Pushing updates to apps that have been delivered to end users is not always an easy task. It depends on the end users to decide whether and when they install the updates.

2 thoughts on “Public Key/Certificates pinning in Java – certificates from Let’s Encrypt

  1. Great article. Not a lot of information around. One question though; Are you sure the public key changes when a certificate is renewed? I was under the impression is does not. Considering that Lets Encrypt certificates last 90 days, it would be annoying to update our frontend clients every 90 days. I thought the public key stayed the same, which the private key is what changes.

    • Hi Miral,
      thank you for your comment. Yes you are right. If the certificate is renewed, the public key will still be the same. I should have said if the server changes its certificate, you as the developer of your own app needs to update your source code as well.
      I am sorry if it made you confused.

      Duc

Leave a Reply

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