SSL Pinning in iOS
Certificate Pinning and Public Key Pinning
Before start about Pinning we should know why we need it?
The answer is, we need it to prevent man-in-the-middle attack. SSL Pinning prevents a man-in-the-middle attack, which means an attacker can not intercept the traffic and modify the data. If an attacker can not intercept the traffic then the application automatically prevents many server-side vulnerabilities. That’s why implementing SSL is very important. Let’s learn about man-in-the-middle attack.
A man-in-the-middle is an attack is a cyberattack where the attacker secretly listens and eventually alters the communication between two parties who believe they are directly communicating with each other.
Attacker gain access to a user’s personal data or the data of some resource a user accesses such as banking data, user credentials, photos, documents, and messages.
Fortunately, there’s a simple way to prevent this kind of attack through a technique called SSL Pinning.
SSL Pinning is a technique that is used on the client-side to avoid a man-in-the-middle attack by validating the expected X509 certificate or public key.
The developers embed (or pin) a list of trustful certificates to the client application during development, and use them to compare against the server certificates during runtime. If there is a mismatch between the server and the local copy of certificates, the connection will simply be disrupted, and no further user data will be even sent to that server. This enforcement ensures that the user devices are communicating only to the dedicated trustful servers.
Types of Pinning
There are two different way to do SSL Pinning
- Certificate Pinning — You can directly pin the SSL certificate by binding the certificate in your applications
- Public Key Pinning — The next method is pinning the certificate’s public key. With this method, you no need to worry about the expiry of the certificate.
Though Certificate Pinning is easiest one, but there is some limitations:
- Less flexibility to change certificates — When the certificate will be expired, you have to release new application by adding the new certificate.
- If server change the certificate, app will not work any more unless change the certificate in application end and republish
You can directly pin the SSL certificate by binding the certificate in your applications. However, it is significant to implement the transition plan before the certificate expires, else older applications will provide errors.
Types of SSL Certificate Pinning
You can choose any one of these three SSL pinning types based on the level of security protection you require.
- Leaf Certificate — Pinning to the Leaf certificate guarantees that your certificate and chain is 100 % valid. However, this type comes with very less expiry time.
- Intermediate Certificate — Signing of the intermediate certificate denotes that you are trusting your CA. If you want to keep your CA, this is the most recommended SSL pinning type.
- Root Certificate — It is also known as self-signed certificates and you can employ this type to sign other documents. You should have a strong certificate validation to ensure CA won’t be compromised.
Let’s start to implement certificate pinning:
First of all we need a copy of our desire server certificate. We can download the certificate using our browser or using OpenSSL Commands. In this tutorial I have used www.google.com as the desire server.
Download using Safari
Download using Chrome
Download Using OpenSSL Command
openssl s_client -connect www.google.com:443 -showcerts < /dev/null | openssl x509 -outform der > certificate.der
Now put the Certificate inside the project main bundle. In this tutorial we have used https://www.google.com/ as our server and Leaf certificate for pinning (Please take a close look in above safari and chrome instructions images to better understanding of different kinds of certificate).
Now it’s time to do some code. Let’s write a class
SSLPinningManager.swift for all service related task. Put the codes below.
private var isCertificatePinning = false
Call above function from your desire class like this:
Now we need to evaluate the
SecTrust with policies and compare the certificate. URL Session delegate function will give us a remote certificate and confirmation about server trust.
We will convert the server certificate and local certificate(app bundle) into Data format and matches both (Server Cert & Local Cert). Let’s see the delegate function below.
If you download and put Leaf Certificate inside your project main bundle, then you have to compare with same certificate in runtime (Take a close look in line no 10 of above image)
That’s the implementation of Certificate Pinning. Please run the code and see the results.
Public Key Pining
Basically public key pinning is more safer because the application will work continuously even after the certificate expires or renew it, as long as you maintain the same public key.
You can retrieve the local certificate’s public key which downloaded earlier and include it in your code as a string. And at runtime, the app compares the remote certificate’s public key received during a network request to the local one.
First we need to save our downloaded certificate to .pem format by extracting and saving its public key. Use below commands to make it done.
openssl x509 -inform der -in certificate.der -pubkey -noout > certificate_public_key.pem
openssl x509 -inform der -in certificate.cer -pubkey -noout > certificate_public_key.pem
After having the .pem formatted file, need to hash it and encode it with base64 encoding, to make it easier to store, and read. For hashing I’m going to use sha256. you can use whatever you like. Use the command below.
cat certificate_public_key.pem | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
The output should be something like this.(For Intermediate Certificate)
Or it’s more easier to do it in one command (I have used here Google for this tutorial), This command return the Intermediate Certificate hash key by default.
openssl s_client -servername www.google.com -connect www.google.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
You can also create the Hash Key from this link too.
Now we are ready with hash key. It’s time to write some code now. Put the code below in our
static let publicKeyHash = "zi6KtWUsUdqG4LN3DOuBQ+NHmIVobjP7ayR5lRvxMEY="private static let rsa2048Asn1Header:[UInt8] = [0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00];
publicKeyHash is the hash key which we get from our certificate.
rsa2048Asn1Header is an array of unsigned integers that contain an indication of the algorithm, and any algorithm parameters, with which the public key is to be used.
If you remember our earlier steps, we used OpenSSL-s dgst function with sha256 hashing to create our hashes. To recreate the same hashes in our code, the
rsa2048Asn1Header bytes are needed.
And include this helper function too. This function will create a hash from the received Data using the sha256 algorithm and return the base64 encoded representation of the hash.
And finally implement the URLSession Delegate function. Please see the code below.
It’s done ! Let’s run and see the output.
I have added here the complete class of
SSLPinningManager.swift. Please have a look.
Copy, paste and feel free use it. Happy Coding :)