When we, developers, are working in the development of any kind of software, we can't forget about security ðŸ”. The minimum security measure we should use is HTTPS as the protocol to share information between a client (in this case, an Android/iOS app) and a server, followed by an updated cryptographic protocol like TLS 1.2 (SSL 3.0 is vulnerable!)
You may think that using an HTTPS is enough but in some cases like banking applications, where sensitive data may be send between our client and our server, could be risky.
By default, when making a TLS connection, the client check two things:
- The server's certificate matches the requested hostname.
- The server's certificate has a chain of truth back to a trusted root certificate.
What it doesn't do is check if the certificate is the specific certificate you know your server is using, and that's a possible security vulnerability: if the client is compromised and a unsafe certificate is installed, someone could do a man-in-the-middle attack.
The solution to this problem is certificate pinning: storing a certificate on our client to ensure that any SSL request made matches the one our server has. Let me explain you how to do it on both Android and iOS apps.
OkHttp lib provide a CertificatePinner class to be added to an OkHttpClient instance. The easiest way to pin a host is turn on pinning with a broken configuration and read the expected configuration when the connection fails.
CertificatePinner certificatePinner = new CertificatePinner.Builder() .add("mydomain.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") .build(); OkHttpClient client = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build();
After a request is executed, you'll see this message on the console:
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure! Peer certificate chain: sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=mydomain.com, OU=PositiveSSL sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root Pinned certificates for mydomain.com: sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= at okhttp3.CertificatePinner.check(CertificatePinner.java) at okhttp3.Connection.upgradeToTls(Connection.java) at okhttp3.Connection.connect(Connection.java) at okhttp3.Connection.connectAndSetOwner(Connection.java)
The exception will provide you the server's certificate public key hashes. Paste them on the CertifinatePinner and done! âœ”
CertificatePinner certificatePinner = new CertificatePinner.Builder() .add("mydomain.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .add("mydomain.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=") .add("mydomain.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=") .add("mydomain.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=") .build();
The iOS solution is not so straightforward because you need to store the certificate itself inside your app. In my case, I've used Alamofire as HTTP client lib for Swift.
First, you need to get the server's certificate in .der format and add it to your iOS project.
openssl s_client -showcerts -servername mydomain.com -connect mydomain.com:443 </dev/null | openssl x509 -outform DER > mydomainCert.der
And now, lets enable certificate pinning: to do it we need both ServerTrustPolicy and SessionManager objects. The first one will define the hostname and certificates that will be used in the process:
var serverTrustPolicies = [ "mydomain.com": .pinCertificates( certificates: ServerTrustPolicy.certificates(), validateCertificateChain: true, validateHost: true ), ]
ServerTrustPolicy.certificates() will return all stored certificates and the booleans will validate the certificate chain and the hostname.
Lastly, create a SessionManager object using this trust policies:
var sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies!))
Done! âœ”. Just use this sessionManager object to execute request
sessionManager.request("https://mydomain.com/api", method: .get, headers: headers)...
Feedback is welcome! Hope it's useful â˜ºï¸