iOS Domain SSL Pinning Through Public Key
Almost all of the iOS apps are using some kind of information exchange with a server to send and retrieve information in order to work. When applications exchange information with a server, they typically leverage the Transport Layer Security (TLS) protocol to provide secure communications.
Background: How TLS works
The TLS protocol is a protocol that establishes secure connections through three main phases:

First phase
When the mobile client sends initiates the connection with the server, it will send various informations:
- The supported TLS versions
- The cipher suite (a set of algorithms) they can use for encryption.
The server will respond with the selected cipher suite and one or more digital certificates. The client verifies that the certificates are valid, and confirms that the server is authentic. Once this validation succeeds, the second phase of verification begins.
Second Phase
The client will now generate a pre-master secret key, that it encrypts and sends to the server using the server’s public key. The server public key is found inside the certificate sent from the server from the first phase.
Once the server receives the encrypted pre-master key, it will decrypt it using its private key. At this point, both the server and the client are able to generate a master secret key based on this pre-master key.
Third phase
With the master secret key available to both sides, the client and server are now exchanging information encrypting and decrypting it using the key. Your communications with the server are now secure.
Certificates
Let’s take a look at the certificates being sent in the first phase. A certificate is a file that encapsulates various information about the server owning that certificate. It’s pretty much the server I.D. card.
A certificate contains the following information:

Let’s briefly review what each information covers:
- Subject: Name of the entity (computer, user, network device, etc.) that the CA issued the certificate to
- Serial Number: Unique identifier for each certificate that a CA issues
- Issuer: Unique name for the CA that issued the certificate
- Valid From: The date and time when the certificate becomes valid
- Valid To: The date and time when the certificate is no longer considered valid
- Public Key: The public key of the key pair that goes with the certificate
- Algorithm Identifier: The algorithm used to sign the certificate
- Digital Signature: A bit string used to verify the authenticity of the certificate
The problem with unpinned SSL validation
During the TLS handshake, the app is verifying a couple of different parameters from the certificate:
- The date of evaluation, which must be from a valid range, using Valid From and Valid To.
- The digital signature validity
When using the iOS default to connect to a server through TLS, you are only checking those parameters. However, the system is designed to consider any valid certificate per the checks above as acceptable to establish the connection. However, this method presents a security concern. An attacker can generate a self-signed certificate and include it in the iOS Trust store. This technique is the first step in a man-in-the-middle attack that allows the attacker to capture all the transmitted data from and to the application. An attacker in this position would be able to analyze how the application works and communicate with the server and even alter the communication between the two actors.
SSL Pinning to the rescue
SSL certificate pinning is the process of configuring the application to reject all but a few selected pre-defined certificate public keys. When the client connects to the server, it will evaluate the pinned keys from your app to the one in the server and establish a connection to the server only if they match. By adding the key inside your code app, no attacker will be able to alter the pinned key, making your communications truly secure.
Most applications choose to include the public certificate of the server in their application. The problem with this approach, while a bit more secure, is that if the server decides to rotate their certificate, the pinning will now fail. If the certificate in the client expires, the pinning will also fail.
While the first approach is acceptable, this causes some concerns for apps at bigger scale that need to support old versions of their app. Since some users are using older versions of the application, they are running into the risk of using a version that is using an expired certificate that will stop working at some point. This approach also requires the mobile developer in charge of the app to actively monitor the expiration date of the certificate and update it in advance before a release (and updating the server one too).
Next, let’s create our tool set to achieve SSL pinning.
Embedded Public certificate method
Apple Security Framework provides a fairly straightforward set of methods to evaluate keys from a certificate file that we can leverage like so:
All you need to do to set up this solution is to add the public certificate from your server in your application bundle. You can get the public certificate by running the following command:
openssl s_client -connect YOUR_DOMAIN:YOUR_DOMAIN_PORT </dev/null \
| openssl x509 -outform DER -out YOUR_FILE_NAME.der
Hardcoded public key method:
In this method we will extract and encode the public key from the certificate in order to include it as a hardcoded string in our application.
In order to do so, you need to download the public certificate and extract the key from it. You can do so by running the following commands: (don’t forget to replace YOUR_DOMAIN everywhere)
openssl s_client -connect YOUR_DOMAIN:YOUR_DOMAIN_PORT -showcerts < /dev/null | openssl x509 -outform DER > YOUR_FILE_NAME.derpython -sBc "from __future__ import print_function;import hashlib;print(hashlib.sha256(open('YOUR_DOMAIN.der','rb').read()).digest(), end='')" | base64openssl x509 -pubkey -noout -in YOUR_DOMAIN.der -inform DER | openssl rsa -outform DER -pubin -in /dev/stdin 2>/dev/null > YOUR_DOMAIN.key.derpython -sBc "from __future__ import print_function;import hashlib;print(hashlib.sha256(open('YOUR_DOMAIN.key.der','rb').read()).digest(), end='')" | base64
This shell command should have as its last output a hash-looking string like so: (example for google.com)
gcc8sRa8B2wwwAmUhutYAHV2N6rnv7PPvenHiLn25NA=
This key is a base64 encoded version of your domain certificate public key. Copy this string and integrate it in your application.
We can now leverage the methods below to compare the server-given public key:
You might be thinking: Cool, I wrote all that complicated code, and now I want to use it in my app!
NSURLSessionDelegate
A lot of apps are using different frameworks for their networking. In order to keep things simple, I will focus on using the Apple `NSURLSessionDelegate` methods. However, know that all of the networking libraries are providing some kind of equivalent or bridging since that is what they are using under the hood.
NSURLSessionDelegate/* If implemented, when a connection level authentication challenge
* has occurred, this delegate will be given the opportunity to
* provide authentication credentials to the underlying
* connection. Some types of authentication will apply to more than
* one request on a given connection to a server (SSL Server Trust
* challenges). If this delegate message is not implemented, the
* behavior will be to use the default handling, which may involve user
* interaction.
*/- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challengecompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

This methods provides a completion block that takes two parameters:
- The disposition (or result to the challenge). Here is what you can use:

- The credential to be used for authentication. In most cases, you can just get the correct one using:
let urlCredential: URLCredential?if let serverTrust = challenge.protectionSpace.serverTrust {
URLCredential(trust: serverTrust)
} else {
urlCredential = nil
}
Hardcoded String method final sample:
Embedded Public certificate method final sample:
Testing implementation
In order to verify that our pinning is working as intended, we can leverage Charles Proxy. This software is a proxy server that acts as a middleware between your client and your server that allows monitoring network activity, pretty much as a man-in-the-middle.
Once Charles is set up, you can try and enable SSL Proxying. By doing so, Charles will now inject their own certificate during the TLS handshake, causing our pinning to fail. You can verify by putting breakpoints in the methods and confirm visually on Charles.


Done!
Congratulations! You now have an app that is better positioned to thwart eavesdropping attempts.