Secure iOS Application with SSL Pinning | Man in the Middle Attack

swift Aug 18, 2023
Secure iOS Application with SSL Pinning | Man in the Middle Attack

What is SSL?

Secure Socket Layer (SSL) is an internet security protocol. It was the predecessor of Transport Layer Security (TLS) and is based on encryption. A secure layer of communication (SSL) establishes authentication, encryption, and data integrity between computers that are on a network.

Websites implementing SSL will have HTTPS in the URL instead of HTTP which means when we are hitting API with HTTPS in its URL it will be more secure instead of HTTP which can be more prone to attacks.

In this article we are going to learn about

  • How is SSL vulnerable?
  • MITM attack
  • What is SSL Pinning?
  • Why SSL Pinning?
  • SSL Pinning implementation
  • Limitations of SSL Pinning

How is SSL vulnerable?

In today's time the use of the internet has increased from web browsing to purchasing shares on the stock market. Everything happens on the internet which is open to all. Many applications use weak SSL protocols and which exposes them to security attacks. If a banking application is using weak SSL protocol then users of the application may lose money to people organising security attacks.

There are many factors which can make SSL vulnerable.

  • Expired Certificates: Most browsers are configured to identify and access websites having valid SSL certificates. Browsers will verify the certificate's status and if it is expired warning will be shown to users but if user accesses the website with expired certificates they are exposed to security attacks.
  • MITM Attack: A Man in the Middle (MITM) attack is a type of cyber crime attack where an attacker intercepts the communication between an application and server. Intercepting will allow attacker to access confidential information which in turn is data theft.

To secure application from MITM attacks we need to implement necessary techniques one of them is SSL pinning.

What is SSL Pinning?

SSL Pinning is a technique where we introduce a certificate between application and server so our connection is secure. Although iOS checks for a valid certificate from its trust store while making connection to server. The store consists of list of certificates authorities that is shipped with iOS. Operating system's validation of certificates can provide a basic layer of security to connection to server but a hacker can still bypass them by a self-signed certificate of hacking the root certificate.

To provide an advanced layer of security we use SSL pinning where we introduce a certificate provided by a valid Certificate Authority (CA). Once the certificate is generated we have two options:

  • Download the certificate provided by the server team and embed it in the application. Whenever an application is connecting to server we can verify if the server's certificate is the same as present in the application's bundle. If it does not match we can refuse to connect.
  • We can also store the public key of the certificate in our application in encrypted string format. Once application connects to server we can verify the server's public key with the one present in application after decrypting it and if it's valid we can proceed to connect.

Why SSL Pinning?

Usually developers rely on operating system to provide security to communications with server which means application does not need to check for the validity of the certificate or even trust certificate or not application is dependent on certificates which are stored in operating systems trust store.

A hacker can easily generate a self-signed certificate and can include it in the system's trust store which will allow the hacker to perform MITM attacks on any application. Hackers can read an application's data passing to server and from server to application. Hackers can extract keys from requests and can use them to get confidential data.

By Implementing SSL pinning for an application we restrict the application to only trust a set of certificates which an hacker may not be able to replicate.

SSL Pinning implementation

For implementing SSL Pinning in application we need to get the certificate so we need to understand first on types of certificates.

  • Root Certificate: The root certificate is the starting point and depth 2 of a chain of trust upon which an SSL certificate is issued. The root certificate belongs to the Certificate Authority. The root certificate is used to issue intermediate certificates, that in term make it possible to register SSL certificates for end users.
  • Intermediate Certificate: This is a depth 1 certificate in the chain of certificates. These certificates inherit the trust level from the root certificate.
  • Leaf Certificate: This is the depth 0 certificate in the chain of certificates. It will have a expiration date and when it expires we need to update the application to support new certificate.

To understand more about certificates go to any website click on the lock icon in the address bar and then search for certificates button. You should see something like this:

Note: If we use Root Certificate for SSL pinning then it won't be a right choice as Operating system already has that certificate in its trust store and it won't affect anything so we should embedded Intermediate and Leaf certificate in the application.

How to embed a certificate in the application:

At first get the server certificate which can be provided by backend or server side team. Then check if the certificate is encoded and make sure it has the .der extension. If not you need to encode it manually by converting the certificate to the .der extension.

Once the above steps are done just drag and drop your certificate into the project navigator and select the target membership based on your requirements.

To extract certificates out of an application's bundle we can create a helper.

struct CertificateHandler {

    static let certificates: [Data] = {
        if let url = Bundle.main.url(forResource: "certificate", withExtension: "der"),
           let data = try? Data(contentsOf: url) {
            return [data]
        }
        return []
    }()
}

Here we created a struct CertificateHandler which has a static property named certificates which is extracting the certificates out of our main bundle of the application.

Now let's deep dive into how can we implement SSL Pinning in our application. We will discuss how we can use URLSession to pin the certificate.

import Foundation

final class NetworkManager {
    
    private let urlSession: URLSession
    
    init() {
        urlSession = URLSession(configuration: .default,
                                delegate: UrlSessionDelegateHandler(),
                                delegateQueue: nil)
    }    
}

final class UrlSessionDelegateHandler: NSObject, URLSessionDelegate {
    
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        
        // Check for certificates count in serverTrust if it's >0 then only proceed
        guard let trust = challenge.protectionSpace.serverTrust, SecTrustGetCertificateCount(trust) > 0 else {
            completionHandler(.performDefaultHandling, nil)
            return
        }
        
        // Get the certificates from SecTrustCopyCertificateChain and extract first certificate
        guard let certificates = SecTrustCopyCertificateChain(trust) as? [SecCertificate],
              let certificate = certificates.first else {
            completionHandler(.performDefaultHandling, nil)
            return
        }
        
        // Convert certificate to Data
        let data = SecCertificateCopyData(certificate) as Data
        
        // Check if our certificate list contains data
        if CertificateHandler.certificates.contains(data) {
            completionHandler(.useCredential, URLCredential(trust: trust))
            return
        } else {
            // Cancel the Authentication Challenge
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
    }
}

Let's understand what we have implemented above:-

  • We created a NetworkManager class which deals with all the network requests for our application.
  • NetworkManager stores a UrlSession object.
  • When we initialise NetworkManager we also initialise urlSession by passing UrlSessionDelegateHandler in the delegateargument.
  • UrlSessionDelegateHandler conforms to URLSessionDelegate which will listen to all the events callbacks given by UrlSession.
  • URLSessionDelegate has a method which will be called when URLSession is trying to verify the certificate presented by the server.
func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, 
                                              URLCredential?) -> Void)

We need to extract the certificate out of the challenge and compare it with the one we have embedded into the application's bundle by using CertificateHandler.

If the certificate is not valid the request can be cancelled by calling:

completionHandler(.cancelAuthenticationChallenge, nil)

If the certificate matches the one in the application's bundle we continue the request by calling:

completionHandler(.useCredential, URLCredential(trust: trust))

If you want to bypass some urls from SSL Pinning we can just add a condition before extracting certificate from challenge:

func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    // Check if hosts contains the URL you want to bypass from SSL Pinning
    if challenge.protectionSpace.host.contains("www.swiftanytime.com") {
        completionHandler(.performDefaultHandling, nil)
        return
    }
    
    // Check for certificates for other hosts
}

Here we want to bypass www.swiftanytime.com from SSL Pinning so we are checking the host name from challenge and returning by calling:

completionHandler(.performDefaultHandling, nil)

Limitations of SSL Pinning

  • It complicates deployment since we are storing hardcoded certificates in the application. Whenever the certificate expires or updates we need to update the application by embedding a new certificate.
  • As a developer we need to think about the ways to find the best way possible to store the certificate in the application which requires development overhead.

Conclusion

SSL Pinning is a technique to safeguard our application from MITM attacks so user's data is secured while communicating with the server. When we discuss mobile app security SSL Pinning is the most important one. SSL Pinning will ensure enhanced security so that the user's device will only interact with trusted servers not hackers'. Further you can go and learn about other techniques of storing certificates in the application which are more safer. You can also deep dive into Root, Intermediate and Leaf certificates to understand their advantages in various scenarios.

Signup now to get notified about our
FREE iOS Workshops!