Everything you need to know about SSL Pinning

Anuj Rai
8 min readMar 21, 2020

--

SSL pinning stands for Secure Socket Layer. SSL certificate creates a foundation of trust by establishing a secure connection.

This connection ensures that all data passed between the web server and browsers remain private and integral. SSL certificates have a key pair, which is a Public and Private key.

These keys work together to establish an encrypted connection. The certificate also contains “Subject“ which is the identity of the certificate/website owner.

SSL Certificate Pinning, or pinning for short, is the process of associating a host with its certificate or public key. Once you know a host’s certificate or public key, you pin it to that host.

Reading this I have one question in my mind, what is the certificate and what other informations this holds?

SSL Pinning

Digital Certificate

A certificate is a file that encapsulates information about the server that owns the certificate. It’s similar to an identification card, such as a passport or a driver license. The structure of certificate uses X.509 standards. X.509 is defined by the International Telecommunication Unions’s standardisation sector. A Certificate Authority(CA) can issue a certificate or it can be self-signed

A Digital Certificates holds many informations -

  1. Subject: Provides the name of the entity (computer, user, network device, etc.) that the CA issued the certificate to.
  2. Serial Number: Provides a unique identifier for each certificate that a CA issues.
  3. Issuer: Provides a unique name for the CA that issued the certificate.
  4. Valid From: Provides the date and time when the certificate becomes valid.
  5. Valid To: Provides the date and time when the certificate is no longer considered valid.
  6. Public Key: Contains the public key of the key pair that goes with the certificate.
  7. Algorithm Identifier: Indicates the algorithm used to sign the certificate.
  8. Digital Signature: A bit string used to verify the authenticity of the certificate.
  9. Version: Version number of certificate
  10. TimeStamp: This shows the time when certificate was created

There are several commonly used filename extensions for digital certificates (X.509) certificates-

A. PEM(Privacy Enhanced Mail): A Base-64 encoding, whose file extension is .pem. The certificate information is enclosed between “ — — -BEGIN CERTIFICATE — — -” and “ — — -END CERTIFICATE — — -”

B. PKCS(Public-key cryptography standards ): Used to exchange public and private objects in a single file. Its extensions are .p7b, .p7c, .p12 etc.

C. DER(Distinguished Encoding Rules): A binary encoding, whose file extensions are .cer, .der and .crt.

Why Do You Need SSL Certificate Pinning?

SSL pinning allows the application to only trust the valid or pre-defined certificate or Public Key. The application developer uses SSL pinning technique as an additional security layer for application traffic. As normally, the application trusts custom certificate and allows the application to intercept the traffic.

Restricting the set of trusted certificates through pinning prevents attackers from analyzing the functionality of the app and the way it communicates with the server.

How SSL works?

  1. Client machine sends a connection request to server, server listens the request.
  2. Server gives response including public key and certificate.
  3. Client checks the certificate and sends a encrypted key to server.
  4. Server decrypt the key and sends encrypted data back to the client machine.
  5. Client receives and decrypt the encrypted data.

Types of SSL Pinning(What to Pin)?

  • Pin the certificate: You can download the server’s certificate and put this in your app bundle. At runtime, the app compares the server’s certificate to the one you’ve embedded.
  • Pin the public key: You can retrieve the certificate’s public key and include it in your code as a string. At runtime, the app compares the certificate’s public key to the one hard-coded hash string in your code.

SSL Pinning in iOS

There are many popular options to perform SSL pining in iOS. These are- URLSession, AlamoFire, AFNetworking, TrustKit. We can implement Certificate pinning as well as public-key pining using URLSession,AlamoFire, AFNetworking but if you are using TrustKit then you can only do public key pinning.

We will use URLSession for Pining in this tutorial. We will use google.co.uk as URL for pining in example project.

Pining using Certificate:

First of all we will download the certificate for www.google.co.uk.

Downloading The Certificate :

Go to the web browser and open www.google.co.uk. When you will open this link you will see the lock icon in the top search bar. Just click on this, you will see one popup, click on show certificate. Drag and drop certificate in your desktop. Please chek below images for the same.

Now just change the name of certificate as per your choice. I have named it google.cer an example project(SSLPinning). Just put this in-app bundle.

Pinning The Certificate

Now in ViewController just go and write the logic of calling API.

import UIKitclass ViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad()guard let url = URL(string: "https://www.google.co.uk") else { return }ServiceManager().callAPI(withURL: url, isCertificatePinning: true) { (message) inlet alert = UIAlertController(title: "SSLPinning", message: message, preferredStyle: .alert)alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))self.present(alert, animated: true, completion: nil)  } }}

The service-related task is done in ServiceManager class.

private var isCertificatePinning: Bool = falsefunc callAPI(withURL url: URL, isCertificatePinning: Bool, completion: @escaping (String) -> Void) {let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil)self.isCertificatePinning = isCertificatePinningvar responseMessage = ""let task = session.dataTask(with: url) { (data, response, error) inif error != nil {print("error: \(error!.localizedDescription): \(error!)")responseMessage = "Pinning failed"} else if data != nil {let str = String(decoding: data!, as: UTF8.self)print("Received data:\n\(str)")if isCertificatePinning {responseMessage = "Certificate pinning is successfully completed"}else {responseMessage = "Public key pinning is successfully completed"}}DispatchQueue.main.async {completion(responseMessage)}}task.resume()}}

Here we are calling URL google.co.uk by URLSession. URL Session has delegate(URLAuthenticationChallenge) which will give us a remote certificate and confirmation about server trust. We convert the server certificate received from server into Data format. Similarly, We convert certificate which is in the App bundle into Data format and matches both (Server cert & Local Cert). Check the below code for the same.

extension ServiceManager: URLSessionDelegate {func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {guard let serverTrust = challenge.protectionSpace.serverTrust else {completionHandler(.cancelAuthenticationChallenge, nil);return}if self.isCertificatePinning {let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)// SSL Policies for domain name checklet policy = NSMutableArray()policy.add(SecPolicyCreateSSL(true, challenge.protectionSpace.host as CFString))//evaluate server certifiactelet isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil)//Local and Remote certificate Datalet remoteCertificateData:NSData =  SecCertificateCopyData(certificate!)//let LocalCertificate = Bundle.main.path(forResource: "github.com", ofType: "cer")let pathToCertificate = Bundle.main.path(forResource: "google", ofType: "cer")let localCertificateData:NSData = NSData(contentsOfFile: pathToCertificate!)!//Compare certificatesif(isServerTrusted && remoteCertificateData.isEqual(to: localCertificateData as Data)){let credential:URLCredential =  URLCredential(trust:serverTrust)print("Certificate pinning is successfully completed")completionHandler(.useCredential,credential)}else {completionHandler(.cancelAuthenticationChallenge,nil)
}
}
}
}

Now we will run the application and see that, certificate pinning is successfuly completed. Check below Screen Shot.

Pinning with Public Key (HashKey)

We can also perform pinning using Public key. This is the prefered way for pinning because in this case we do not need to update the key every time, when certificate is updated.

Even though certificates are being rotated, the underlying public key within the certificate remains the same. At least, you have the option to keep the same one. Google’s does remain static. Therefore, pinning the key makes the design more flexible, but a bit trickier to implement, as now we have to extract the key from the certificate, both at pinning time and at every connection.

How we will get the public key?

You can get this from certificate also as well as if you want to get this from command line then below is the command for the same -

openssl s_client -connect the.host.name:443 | openssl x509 -pubkey -noout

In our case we are using google.co.uk so the below command will print out public key

openssl s_client -connect www.google.co.uk:443 | openssl x509 -pubkey -nooutBelow is the public key-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyF4KwTZBKHE+4NKftRbfy4Vo2G9igdD4GbE+wmYjouLqqACPwXLZIROvKnLrvDuvftyiwnjXFu1p1Dc8taDtEdFIDnbzGpBd2IoKHUijb+KPGKvczYpXXpISpO/0u+jOYRnPkBbCNnnxBvA+d0TSTe01UctrfN5TCxonX4E/YZMrxd9lrn5mwMGOC17ZEQIcfwPvxDl5g0LbOBFzV62O4Spt0610ui6T5rFzxuFXD5W108/GMrqtti8yo2b4ouHC2fAcM+Gfel5n136H6/Q0wJd+7oO569al1TZL8YMk37P5Uxp2TxfjXFi/61ARGarp1eEtrWTbfGhFmaZicym0wIDAQAB-----END PUBLIC KEY-----

In this case, we will not store the actual certificates in the application. we will create sha256 hashes and store them instead. This has several advantages. its easier to manage due to size, and it allows shipping an application with a hash of a future (or a backup) certificate or key without exposing them ahead of time.

Creating HashKey

We can create hash key by this link also https://www.ssllabs.com/ssltest. If you want to create by terminal then just copy and paste below command

openssl s_client -servername www.google.co.uk -connect www.google.co.uk:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

The output of this command gives you the hash-key (SHA256)-

Gdbmf0GLeR880mGN9WSW1XOL6v7xsVmWO6ks0LxybzU=

You can also verify this hash base64 encoded key from above mentioned URL. In my case, this is the same, below is the screen shot.

Now we will change the value of `isCertificatePinning` to false in Api calling the method in view controller. Now view controller would look like -

override func viewDidLoad() {super.viewDidLoad()guard let url = URL(string: "https://www.google.co.uk") else { return }ServiceManager().callAPI(withURL: url, isCertificatePinning: false) { (message) inlet alert = UIAlertController(title: "SSLPinning", message: message, preferredStyle: .alert)alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))self.present(alert, animated: true, completion: nil) }}

Now we will add below code in the service manager class

static let publicKeyHash = "Gdbmf0GLeR880mGN9WSW1XOL6v7xsVmWO6ks0LxybzU="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]private func sha256(data : Data) -> String {var keyWithHeader = Data(rsa2048Asn1Header)keyWithHeader.append(data)var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))keyWithHeader.withUnsafeBytes {_ = CC_SHA256($0, CC_LONG(keyWithHeader.count), &hash)}return Data(hash).base64EncodedString()}

Now we will add below code in service manager extension (Url session delegate method)

if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {// Server public keylet serverPublicKey = SecCertificateCopyKey(serverCertificate)let serverPublicKeyData = SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!let data:Data = serverPublicKeyData as Data// Server Hash keylet serverHashKey = sha256(data: data)// Local Hash Keylet publickKeyLocal = type(of: self).publicKeyHashif (serverHashKey == publickKeyLocal) {// Success! This is our serverprint("Public key pinning is successfully completed")completionHandler(.useCredential, URLCredential(trust:serverTrust))return}}

Once we will test this, we get a pinning success message on the screen. Please check the below screenshot.

For full source code, you can mail me at anuj.rai2489@gmail.com or can visit this link.

--

--

Anuj Rai

Senior iOS Developer #Swift #iOS #Apple #ObjectiveC