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.
 Android
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();
iOS
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 ☺ï¸
Sources
OkHttp: https://github.com/square/okhttp/wiki/HTTPS
Alamofire: https://github.com/Alamofire/Alamofire#security
Top comments (1)
HI Daniel, this is great blog post, i was thinking to write one, but found this one, so instead creating new one i would append my post content here :),
Along with the content metioned by Daniel, we can also generate sha key from terminal using public certificate received from
openssl s_connect
command.First create a file using touch command say
touch test.crt
, then runopenssl s_client -connect www.google.com:443
copy content beginning from-----BEGIN CERTIFICATE----- to... -----END CERTIFICATE-----
in test.crt after that run this commandopenssl x509 -in test.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | \
this most probably should give you correct value. For more information please refer this page developer.mozilla.org/en-US/docs/W...openssl enc -base64