Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

SSL Certificate Pinning in iOS Applications

DZone's Guide to

SSL Certificate Pinning in iOS Applications

Certificate pinning adds an extra layer of security to your iOS mobile app by verifying backend communications without relying on a third party.

· Mobile Zone
Free Resource

Get gorgeous, multi-touch charts for your iOS application with just a few lines of code.

In this day and age, more and more user data is stored electronically. Users are expecting end-to-end security from every application they are installing on their devices. Application developers, too, are seeking to secure all communications between their apps and backends in order to prevent hackers from reverse engineering their protocols and getting access to their databases.

The most basic form of security when transferring data between the application and the service backend is SSL/TLS encryption, and it is very common for developers today to switch their traffic to https and declare their communications as secure. In fact, mobile platforms today make it really hard for developers not to use https. That by itself, however, is not enough. Encryption is useless when communicating parties can not validate the identity of their peers.

There are mechanisms that try to solve the identity problem by means of chain of trust, having certificate authorities sign the certificates and having operating systems validating the certificates/identities when establishing SSL connections. However, these are not fool-proof and are known to be easily bypassed in hostile environments. A Man in the Middle Attack (MITM) is always a possibility when you are relying on others to validate the address and the identity of the second party.

The idea of certificate pinning is to stop relying on these third-parties (certificate authorities, operating systems) to validate the identity of the backend server and to take matters into our own hands. Knowing in advance the certificate of the server our application is communication with, we can hard-wire it into the application itself and refuse to communicate unless it is a perfect match.

At Bugsee, we rely heavily on certificate pinning. The nature of the data we collect might be sensitive at times- after all, we do record the last minute of screen video, console logs, network traffic and other activity that happened in our customers' apps right before a bug or a crash. Even though we do have mechanisms to remove private data, the logs and to hide any sensitive views from the videos, we still take security seriously and make sure the data from SDKs will only arrive at our servers and could not be intercepted in transit.

How to Pin

The basic idea is not to reinvent the wheel and keep using the existing secure channels and protocols, but to harden the security further by introducing an extra identity check performed by the application itself. The application will obtain the identity of the server upon connection and compare it to the one hardcoded in the application itself. We may even decide to whitelist a set of such identities within the application if it's required by our environment. As to what exactly to pin, there are two options available:

  • Certificate: This is easy to obtain and implement, however, it comes with a major drawback. Certificates do tend to expire every year or two. If we control the certificate, we must update the application and add the new future certificate to our pin set prior to switching the on the server. It may still not be enough, as user adoption of the new versions of the applications is never 100%; there will be a chance users with older versions of the application will get locked out, and this has to be handled explicitly. Some services may decide to rotate certificates even more frequently (Google rotates its SSL certificates once a month). Playing constant catch up and being forced to release a new version of the application just to update the pinned certificates might not be a good idea in such cases.
  • Public key: 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.

In either case, we will not store the actual certificates and/or keys 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.

Obtaining the Certificate and the Public Key

For the sake of this example, we will try to pin www.google.com and use it from our example application.

Obtain the certificate and create a sha256 hash from it; for convenience, we will encode the hash with base64:

openssl s_client -connect www.google.com:443 -showcerts < /dev/null | openssl x509 -outform DER > google.der
python -sBc "from __future__ import print_function;import hashlib;print(hashlib.sha256(open('google.der','rb').read()).digest(), end='')" | base64
KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E=

Extract the public key from the certificate and hash it as well:

openssl x509 -pubkey -noout -in google.der -inform DER | openssl rsa -outform DER -pubin -in /dev/stdin 2>/dev/null > googlekey.der
python -sBc "from __future__ import print_function;import hashlib;print(hashlib.sha256(open('googlekey.der','rb').read()).digest(), end='')" | base64
4xVxzbEegwDBoyoGoJlKcwGM7hyquoFg4l+9um5oPOI=

Implementing Certificate Pinning on Ios

Now that we have hashes of both the certificate and the underlying public key, let's implement the checking every time the connection is being established.

First we will implement a URLSessionPinningDelegate class with the following code:

import Foundation
import Security

class URLSessionPinningDelegate: NSObject, URLSessionDelegate {

    let pinnedCertificateHash = "KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E="
    let pinnedPublicKeyHash = "4xVxzbEegwDBoyoGoJlKcwGM7hyquoFg4l+9um5oPOI="

    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(bytes: 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()
    }

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {

        if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
            if let serverTrust = challenge.protectionSpace.serverTrust {
                var secresult = SecTrustResultType.invalid
                let status = SecTrustEvaluate(serverTrust, &secresult)

                if(errSecSuccess == status) {
                    print(SecTrustGetCertificateCount(serverTrust))
                    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {

                        // Certificate pinning, uncomment to use this instead of public key pinning
//                        let serverCertificateData:NSData = SecCertificateCopyData(serverCertificate)
//                        let certHash = sha256(data: serverCertificateData as Data)
//                        if (certHash == pinnedCertificateHash) {
//                            // Success! This is our server
//                            completionHandler(.useCredential, URLCredential(trust:serverTrust))
//                            return
//                        }

                        // Public key pinning
                        let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
                        let serverPublicKeyData:NSData = SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
                        let keyHash = sha256(data: serverPublicKeyData as Data)
                        if (keyHash == pinnedPublicKeyHash) {
                            // Success! This is our server
                            completionHandler(.useCredential, URLCredential(trust:serverTrust))
                            return
                        }

                    }
                }
            }
        }

        // Pinning failed
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

Now, when we create URLSession, we must set this as the delegate to make sure connection is verified properly:

        if let url = NSURL(string: "https://www.google.com/") {

            let session = URLSession(
                configuration: URLSessionConfiguration.ephemeral,
                delegate: URLSessionPinningDelegate(),
                delegateQueue: nil)

            let task = session.dataTask(with: url as URL, completionHandler: { (data, response, error) -> Void in
                if error != nil {
                    print("error: \(error!.localizedDescription))")
                } else if data != nil {
                    if let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) {
                        print("Received data:\n\(str)")
                    }
                    else {
                        print("Unable to convert data to text")
                    }
                }
            })
            
            task.resume()
        }
        else {
            print("Unable to create NSURL")
        }

Even though SSL/TLS is considered to be mandatory when implementing client/server communications these days, relying on chain of trust to verify the identity of the server is not enough, as MITM (Man in the Middle) attacks are still a possibility. It is much safer to rely on an additional layer of protection, since we have the luxury of pinning the exact certificate or the public key we are expecting on the other side. We’ve learned how to extract a server key as well as how to implement the pinning on iOS. Stay tuned; in our next tutorial we will cover the methods to implement certificate pinning on Android.

For convenience, the full source of the example is available on GitHub.

.Net developers: use Highcharts, the industry's leading interactive charting library, without writing a single line of JavaScript.

Topics:
ios ,ssl/tls ,ssl certificates ,mobile ,mobile security

Published at DZone with permission of Dmitry Fink. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}