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
This is great.
How do we get “validKeysList” required in this line
if(validKeysList.contains(f))
This article is somewhat out of date by now. We have a builtin approach that works better. See: https://www.codenameone.com/javadoc/com/codename1/io/ConnectionRequest.html#checkSSLCertificates-com.codename1.io.ConnectionRequest.SSLCertificate:A-
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
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();
}
This is before the call. I specifically pointed at checkSSLCertificates which is a callback that will be invoked when the request data arrives.
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());
}