Certificate Pinning iOS

Photo of Karol Piątek

Karol Piątek

Updated Dec 17, 2024 • 20 min read
robert-anasch-y_ZPwFTCp84-unsplash

Certificate pinning is one of the basic security mechanisms of network communication. Every developer should be aware of it.

The OWASP security organization includes it in their “General Best Practices” and “iOS Specific Best Practices ”. This shows how important this topic is, especially for iOS apps, where SSL pinning enhances security by ensuring that only trusted SSL certificates can establish secure connections.

This article discusses certificate pinning and related topics. The certificate file plays a crucial role in SSL pinning, as it is used to establish secure connections and ensure proper configuration for enhanced security.

You will get familiar with things like:

  • TLS/SSL and its weaknesses
  • Certificate pinning
  • What is recommended and how to implement it on iOS

Theory

What is TLS? (Transport Layer Security)

TLS is an extended version of the SSL protocol. It is a cryptographic protocol used to encrypt network traffic. Your browser uses an implementation of that protocol - HTTPS. It is usually displayed next to the website address as a padlock icon.

SSL pinning is a crucial technique that binds a specific SSL certificate to a web server, preventing man-in-the-middle attacks and ensuring a secure connection between mobile applications and servers.

How TLS works?

Formal definition

TLS uses asymmetric cryptography to provide secure data transportation. Asymmetric cryptography uses two keys, a public key and a private key. The public key is used to encrypt data and the private key decrypts previously encrypted data. When you make a connection with a server you exchange public keys with it. You receive the public key to encrypt data before sending it. The server receives your public key so you can decrypt the data received from it with your private key. The keys are uniquely generated for each connection and are based on a shared secret negotiated at the beginning of the session, also known as a TLS handshake.

Example by analogy

TLS works like a mailbox. Everyone has access to the mailbox and can put a letter inside it, but you need a key to open the mailbox and read letters. Only the person who created the mailbox has the key. TLS is considered undecryptable in reasonable time. This means that the best locksmith would have to work on it for a long time to open it without having a key.

You can also check out this video visualising asymmetric encryption.

Some Dates

  • SSL protocol is deprecated since 2015.
  • TLS 1.0/1.1 is deprecated since march 2020.
  • There are no plans to deprecate TLS 1.2/1.3 yet.

What is a certificate?

A certificate is a data file containing a public key and other information like expiration date, organization name, and others. It allows us to encrypt a connection using the public key. During the authentication process, the server's certificate is compared against stored credentials to ensure secure communication and protect sensitive data from potential interception.

What is certificate pinning?

Certificate pinning is a security technique used to associate a specific server certificate or public key with a mobile app, ensuring that the app only trusts and communicates with the expected server. This technique is crucial in preventing man-in-the-middle (MITM) attacks, where an attacker intercepts and modifies the communication between the app and the server.

By pinning the server’s certificate or public key, the app can verify the identity of the server and ensure that the communication is secure. This added layer of security helps protect sensitive data and maintain the integrity of the communication channel.

So what is the problem with using only the TLS protocol for networking?

Imagine you are using a public Wi-Fi network on the train. That network might be created by someone who wants to read the data sent by you. Since that person is providing your internet connection, they can create their own certificate and tell you to encrypt your data with it. Because this person created the certificate, they can read all the data inside it and then send it further to the right receiver. You may not see that you’re using the wrong certificate. This technique is called a man-in-the-middle attack. This illustration shows how it works.

Attackers can also exploit vulnerabilities in mobile applications to bypass certificate pinning, using methods like reverse engineering the app and tools such as MobSF and Frida to manipulate the app's behavior and security measures.

Codestories-image-mitm-diagram

How to prevent using invalid certificates?

This is where certificate pinning and public key pinning come in. These approaches consist of defining a list of valid certificates/public keys. Every time the app sends data, it checks if that certificate is on the list of trusted certificates/public keys. This list of certificates/public keys may be hard-coded or defined in a file. Both solutions prevent man-in-the-middle attacks and should pass a penetration test. However, the SSL pinning implementation can be challenging for developers due to the need to frequently update the pinned certificates and handle potential issues during the update process.

Despite these challenges, implementing SSL pinning is crucial for enhancing the security of mobile applications. If you want to be even more secure, you may also encrypt the certificate to make it harder to get.

More formal definition:

“Server/Host is associated with a certificate or public key. You configure the app to reject all certificates other than the predefined certificates or public keys. Whenever the app connects to a server, it compares the server certificate with the pinned certificate(s) or public key(s). If and only if they match, the app trusts the server and establishes the connection.”

Certificate pinning vs public key pinning

Public key pinning is a simplified implementation of certificate pinning. Unlike certificate pinning, it only validates the public key instead of the whole certificate.

The public key doesn’t have an expiration date. So if we have implemented public key pinning, we can regenerate your certificate with the same public key. This allows us to regenerate the certificate without uploading a new version of the app. But, since it doesn’t change, it may violate key rotation policies.

Then there is certificate pinning. It validates the whole certificate, so this implementation is more secure, since even a small change in the certificate will cause an issue. It has an expiration date, so every time you create a new certificate you have to upload a new version of the app. In the context of iOS applications, iOS certificate pinning is crucial for validating server certificates. Techniques like Alamofire certificate pinning and NSURLSession are commonly used to ensure secure communication between apps and servers. This practice is also highlighted in essential security guidelines like those from OWASP.

Usually, the heavier your security implementation, the less flexible and harder to maintain it gets. You should always look for a solution that fits your app.

  • If you really care about security and you can do it at the expense of flexibility, you should choose hardcoded certificate pinning. This solution should be used for example in banking applications - it is the safest of those presented here.

  • If your certificates change very often and security isn’t crucial in your project, you should probably use public key pinning. It will give you more flexibility and you will not block users with older versions of your application.

  • If you want to support older versions of the app, but still implement certificate pinning, you can create a procedure to download the new certificate after expiration. It is not as safe as a hardcoded one, but it gives you more flexibility. After the certificate expires, you will download the new one. While you are downloading the new certificate, there is a chance that someone is listening to your communication.

  • If your mobile application does not need additional protection, e.g. it uses a public API to display the weather, you do not have to implement certificate pinning.

Types of Certificate Pinning

There are two main types of certificate pinning:

  1. Certificate Pinning: This method involves pinning the entire server certificate, including the public key, issuer, and serial number. It provides the highest level of security because it validates the entire certificate. However, it can be more complex to implement and maintain, especially when certificates need to be updated or rotated.

  2. Public Key Pinning: This method involves pinning only the public key of the server certificate. It is simpler to implement compared to certificate pinning and offers more flexibility. Since the public key does not change as frequently as the entire certificate, this method allows for easier certificate updates without requiring changes to the app. However, it provides a lower level of security compared to certificate pinning.

Choosing between these two methods depends on the specific security requirements and flexibility needed for your app.

Is that all? Am I safe now?

Well, no. There are still techniques to fool certificate pinning, but using it makes it extremely hard to decrypt network traffic. You can read about some bypasses here: Certificate pinning bypass . Also there are some advanced techniques to prevent such bypasses: Prevent bypassing of certificate pinning.

Additionally, it's crucial to incorporate security features without altering the mobile app code.

AlamoFire is a good choice when you are already using that or you are looking for a network library. If networking in your app uses NSURLSession and you only need certificate pinning, TrustKit is a good option.

You shouldn’t implement pinning by yourself, as implementation mistakes are extremely likely and usually lead to severe vulnerabilities.

OWASP’s stance on certificate pinning

OWASP (Open Web Application Security Project) is an organization that provides unbiased and practical, cost-effective information about computer and Internet applications. This organization is well known. A lot of big companies use “OWASP top 10 application security vulnerabilities” in their pentesting plans.

You can find that certificate pinning is listed among the “General Best Practices” and “iOS Specific Best Practices ” on the OWASP website.

“You should pin anytime you want to be relatively certain of the remote host's identity or when operating in a hostile environment. Since one or both are almost always true, you should probably pin all the time.” - OWASP pinning cheat sheet

Using certificate pinning in practice

How to get an example certificate.der?

Run this command in your terminal, you should get Google’s certificate with the .der extension:

openssl s_client -connect google.com:443 </dev/null | openssl x509 -outform DER -out google.com.der

Alamofire certificate pinning

The Alamofire library has a built-in class to handle certificate pinning: ServerTrustManager.

Let’s see the initialize method of this class:

init(allHostsMustBeEvaluated: Bool , evaluators: [String: ServerTrustEvaluating])

Evaluators - a dictionary with the website address as a key and the list of defined certificates wrapped in the ServerTrustEvaluating class as a value of it.

allHostsMustBeEvaluated: Bool -

If true: Alamofire will only allow communication with hosts defined in the evaluators and matching the defined certificates.

If false: Alamofire will check certificates only for hosts defined in the evaluators dictionary. Communication with other hosts will not use certificate pinning

Let’s create a ServerTrustManager for your project!

Add your certificate to your project and add an extension class to read that certificate.

struct Certificates {
    
    static let certificate: SecCertificate = Certificates.certificate(filename: "certificateFileName")
  
    private static func certificate(filename: String) -> SecCertificate {
        
        let filePath = Bundle.main.path(forResource: filename, ofType: "fileExtension")!
        let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
        let certificate = SecCertificateCreateWithData(nil, data as CFData)!
        
        return certificate
  }
}

Now let’s set up the networking class, which will pin that certificate.

import Alamofire

class AlamofireNetworking {
    
    private let certificates = [
      "www.yourwebsite.com":
        PinnedCertificatesTrustEvaluator(certificates: [Certificates.certificate],
                                         acceptSelfSignedCertificates: false,
                                         performDefaultValidation: true,
                                         validateHost: true)
    ]

    private let session: Session
    
    init(allHostsMustBeEvaluated: Bool) {
        
        let serverTrustPolicy = ServerTrustManager(
            allHostsMustBeEvaluated: allHostsMustBeEvaluated,
            evaluators: certificates
        )
        
        session = Session(serverTrustManager: serverTrustPolicy)
    }
    
    func request(_ convertible: URLRequestConvertible) -> DataRequest {
      return session.request(convertible)
    }
}

The implementation on iOS app is really easy and readable here. I you use the request method from that class, certificate pinning will work. You can see the usage of this class in the unit tests of the project . You can also read the documentation for more information.

TrustKit public key pinning

First, install TrustKit in your project. You can do this using cocoaPods or carthage. Use the instruction s to do this.

In order to implement certificate pinning with TrustKit, we have to define the configuration for this tool. To do so, we need to extract publicKeyHashes from our domain. You can easily get them using script provided by TrustKit. Just follow the instruction s in their documentation.

Here is a complete class with an URLSession which uses certificate pinning.

final class TrustKitCertificatePinning: NSObject, URLSessionDelegate {
    
    /// URLSession with configured certificate pinning
    lazy var session: URLSession = {
        URLSession(configuration: URLSessionConfiguration.ephemeral,
                   delegate: self,
                   delegateQueue: OperationQueue.main)
    }()
    
    private let trustKitConfig = [
        kTSKPinnedDomains: [
            "www.yourwebsite.com": [
kTSKDisableDefaultReportUri: true, /// Disable reporting errors to default domain. kTSKEnforcePinning: true, kTSKIncludeSubdomains: true, kTSKExpirationDate: "2020-10-09", kTSKPublicKeyHashes: [ "GesrhCSBz+OCxCt624nic/qqLXAPGUGGf5vwB5jBheU=", "+7YVLndnzqU0VtEREXo00bJlgdmQ9T9qy2IWVVWNpcE=", ], ] ] ] as [String : Any] override init() { TrustKit.initSharedInstance(withConfiguration: trustKitConfig) super.init() } // MARK: TrustKit Pinning Reference func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) == false { // TrustKit did not handle this challenge: perhaps it was not for server trust // or the domain was not pinned. Fall back to the default behavior completionHandler(.performDefaultHandling, nil) } } }

As you can see, there is not much to talk about here, but let’s dig into that code.

This part of code defines configuration for TrustKit. You can define the domains that you want to check.
private let trustKitConfig = [
        kTSKPinnedDomains: [
            "www.yourwebsite.com": [
kTSKDisableDefaultReportUri: true, /// Disable reporting errors to default domain. kTSKEnforcePinning: true, kTSKIncludeSubdomains: true, kTSKExpirationDate: "2020-10-09", kTSKPublicKeyHashes: [ "GesrhCSBz+OCxCt624nic/qqLXAPGUGGf5vwB5jBheU=", "+7YVLndnzqU0VtEREXo00bJlgdmQ9T9qy2IWVVWNpcE=", ], ] ] ] as [String : Any]

You can read more about configuration in the documentation .

To get TrustKit working, you have to initialize a TrustKit shared instance with this configuration.
TrustKit.initSharedInstance(withConfiguration: trustKitConfig)

Then you can use that sharedInstance in the urlSession delegate methods to validate your certificates. This is the default implementation from TrustKit’s documentation.

func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                                                
    if TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) == false {
        // TrustKit did not handle this challenge: perhaps it was not for server trust
        // or the domain was not pinned. Fall back to the default behavior
        completionHandler(.performDefaultHandling, nil)
    }
}

The last thing you need to do is assign the created Delegate to your session.

final class TrustKitCertificatePinning: NSObject, URLSessionDelegate {
    
    /// URLSession with configured certificate pinning
    lazy var session: URLSession = {
        URLSession(configuration: URLSessionConfiguration.ephemeral,
                   delegate: self,
                   delegateQueue: OperationQueue.main)
     }()

Use your URLSession with certificate pinning :)

Complete CertificatePinning project with unit tests:

https://github.com/karolpiateknet/CertificatePinning

Advanced Certificate Pinning Techniques

To further enhance the security of certificate pinning, several advanced techniques can be employed:

  1. Certificate Chain Pinning: This technique involves pinning the entire certificate chain, including the root certificate, intermediate certificates, and the server certificate. By validating the entire chain, this method ensures that all certificates in the chain are trusted, providing a higher level of security.

  2. Public Key Hash Pinning: Instead of pinning the public key itself, this method involves pinning the hash of the public key. This adds an additional layer of security against key compromise, as the hash is a unique representation of the public key.

  3. Certificate Transparency: This technique involves using certificate transparency logs to monitor and verify the issuance of certificates for the server. By checking these logs, you can detect any unauthorized or malicious certificates issued for your domain, further enhancing the security of your app.

Implementing these advanced techniques can significantly improve the security of your app’s communication with the server.

Best Practices for Secure Certificate Pinning

To ensure secure certificate pinning, the following best practices should be followed:

  1. Use a Trusted Certificate Authority: Always use a trusted certificate authority to issue your server certificate. This ensures that the certificate is legitimate and recognized by major browsers and operating systems.

  2. Use a Secure Certificate: Ensure that your server certificate is secure, with a strong public key and a valid certificate chain. This helps prevent unauthorized access and ensures the integrity of the communication.

  3. Pin the Certificate or Public Key: Pin the server certificate or public key to your mobile app. This ensures that the app only trusts the expected server and prevents man-in-the-middle attacks.

  4. Use a Secure Storage: Store the pinned certificate or public key in a secure storage, such as a secure key store. This prevents unauthorized access to the pinned data and ensures its integrity.

  5. Regularly Update the Certificate: Regularly update the server certificate and pinned certificate or public key to ensure that the app remains secure. This helps prevent issues related to certificate expiration and key compromise.

By following these best practices, you can ensure that your app’s communication with the server is secure and protected against potential attacks.

Common Challenges and Limitations

Summary

Thank you for your time. I hope you enjoyed reading my article and learned a lot of useful information.

Photo of Karol Piątek

More posts by this author

Karol Piątek

Senior iOS developer
Efficient software development  Build faster, deliver more  Start now!

Read more on our Blog

Check out the knowledge base collected and distilled by experienced professionals.

We're Netguru

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency.

Let's talk business