Open Source & Free  

Certificate Verification, Avoid SSL Pinning Vulnerability

Certificate Verification, Avoid SSL Pinning Vulnerability

Header Image

Certificate pinning is a security measure designed to thwart potentially dangerous and complex attacks. Since those sort of attacks are pretty hard to execute it’s a security measure that is probably unnecessary for most developers. However, if you are building an application for a very sensitive industry (e.g. Government, Banking etc.) you might be required to include this defensive measure.

When we connect to an HTTPS server our client checks the certificate on the server. If the certificate was issued by a trusted certificate authority then the connection goes thru otherwise it fails. Let’s imagine a case where I’m sitting in a coffee shop connected to the local wifi, I try to connect to gmail to check my email. Since I use HTTPS to Google I trust my connection is secure.

What if the coffee shop was hacked and the router is listening in on everything?

So HTTPS is encrypted and the way encryption works is thru the certificate. The server sends me a certificate and we can use that to send encrypted data to it.

What if the router grabs the servers certificate and communicates with Google in my name?

This won’t work since the data we send to the server is encrypted with the certificate from the server.

So what if the router sends its own “fake certificate”?

That won’t work either. All certificates are signed by a “certificate authority” indicating that a google.com certificate is valid.

What if I was able to get my fake certificate authorized by a real certificate authority?

That’s a problem!

It’s obviously hard to do but if someone was able to do this he could execute a “man in the middle” attack as described above. People were able to fool certificate authorities in the past and gain fake certificates using various methods so this is possible and probably doable for any government level attacker.

Certificate Pinning

This is the attack certificate pinning (or SSL pinning) aims to prevent. We code into our app the “fingerprint” of the certificate that is “good” and thus prevent the app from working when the certificate is “fake”. This might break the app if we replace the certificate at some point but that might be reasonable in such a case.

To do this we introduced a new cn1lib. that fetches the certificate fingerprint from the server, we can just check this fingerprint against a list of “authorized” keys to decide whether it is valid. You can install the SSLCertificateFingerprint from the extensions section in Codename One Settings and use something like this to verify your server:

if(CheckCert.isCertCheckingSupported()) {
    String f = CheckCert.getFingerprint(myHttpsURL);
    if(validKeysList.contains(f)) {
        // OK it's a good certificate proceed
    } else {
       if(Dialog.show("Security Warning", "WARNING: it is possible your commmunications are being tampered! We suggest quitting the app at once!", "Quit", "Continue")) {
          Display.getInstance().exitApplication();
       }
    }
} else {
    // certificate fingerprint checking isn't supported on this platform... It's your decision whether to proceed or not
}

Notice that once connection is established you don’t need to verify again for the current application run.

6 Comments

  • Eric Kimotho says:

    This is great.

    How do we get “validKeysList” required in this line

    if(validKeysList.contains(f))

  • Eric Kimotho says:

    Thank you. I tried to follow link above and came up snippet below. But length of SSLCertificate Array is 0 for all https url i tried. Please note i am using synchronous version of ConnectionRequest to be able to update UI incase network error. Please assist with correct implementation

    • Eric Kimotho says:

      private void certPinning() {

      ConnectionRequest request = new ConnectionRequest();
      request.setUrl(myHttpsUrl);

      request.setCheckSSLCertificates(true);

      switch (Display.getInstance().getPlatformName()) {
      case “and”:
      case “ios”:
      try {
      if (request.canGetSSLCertificates()
      && getSSLCertArray(request).length > 0) {

      new SignIn().show();
      } else {
      showWarningAlert(“Connection Alert”,
      “Secure Connection is tampered, quit app now”);
      }
      } catch (IOException e) {
      Log.p(“Exception ” + e.getMessage());
      }
      break;

      case “win”:
      new SignIn().show();
      break;
      }

      }

      private SSLCertificate[] getSSLCertArray(ConnectionRequest request)
      throws IOException {
      Log.p(\nCert Length ” + request.getSSLCertificates().length);
      return request.getSSLCertificates();
      }

      • Shai Almog says:

        This is before the call. I specifically pointed at checkSSLCertificates which is a callback that will be invoked when the request data arrives.

        • Eric Kimotho says:

          At least using below implementations i can get certificate length as 2, Please Confirm if these implementations are okay

          //Synchronous Implementation

          private void certPinning() {

          ConnectionRequest request = new ConnectionRequest();

          request.setUrl(myHttpsUrl);
          request.setHttpMethod(“POST”);
          request.setTimeout(15000);
          request.setReadTimeout(20000);
          request.addArgument(“dataTag”, “request data”);
          request.setFailSilently(true);
          request.setCheckSSLCertificates(true);

          NetworkManager.getInstance().addToQueueAndWait(request);

          switch (request.getResponseCode()) {

          case 0:
          case 404:
          //Connection error
          break;

          case 200:
          byte[] result = request.getResponseData();
          String resp = new String(result);
          break;
          }

          switch (Display.getInstance().getPlatformName()) {
          case “and”:
          case “ios”:
          try {

          if (request.canGetSSLCertificates()
          && getSSLCertArray(request).length > 0) {
          //Continue
          } else {
          showWarningAlert(“Connection Alert”,
          “Secure Connection is tampered, quit app now”);
          }
          } catch (IOException e) {

          }
          break;
          case “win”:
          //Continue
          break;
          }
          }

          private SSLCertificate[] getSSLCertArray(ConnectionRequest request)
          throws IOException {
          Log.p(\nCert Length ” + request.getSSLCertificates().length);
          return request.getSSLCertificates();
          }
          ==============================================
          //Asynchronous Implementation
          //Seems checkSSLCertificates function requires Asynchronous version of ConnectionRequest

          private void certPinning() {
          String resp;
          ConnectionRequest request = new ConnectionRequest(){

          @Override
          protected void checkSSLCertificates(SSLCertificate[] certificates) {

          Log.p(“Cert Len ” + certificates.length);

          switch (Display.getInstance().getPlatformName()) {
          case “and”:
          case “ios”:
          if (certificates.length == 0) {

          Log.p(“Secure Connection is tampered, quit app now”);
          } else {
          //Continue
          }
          break;

          case “win”:
          //Continue
          break;
          }

          }
          }

          @Override
          protected void readResponse(InputStream input) throws IOException {
          result = Result.fromContent(input, Result.JSON);
          resp = result.getAsString(“root”);
          }

          @Override
          protected void postResponse() {
          Log.p(resp);
          }

          request.setUrl(myHttpsUrl);
          request.setHttpMethod(“POST”);
          request.setTimeout(15000);
          request.setReadTimeout(20000);
          request.addArgument(“dataTag”, “request data”);
          request.setFailSilently(true);
          request.setCheckSSLCertificates(true);

          NetworkManager.getInstance().addToQueue(request);
          NetworkManager.getInstance().addErrorListener((e) -> e.consume());

          }

Leave a Reply