Certificate pinning and Public key pinning

· 3 min read
Certificate pinning and Public key pinning
Certificate pinning in Swift

Certificate pinning and public key pinning are both techniques that are used to enhance the security of a network connection by ensuring that the client trusts only a specific certificate or public key.

Certificate pinning

Certificate pinning is a security technique that is used to ensure that an app is communicating with the correct server. Certificate pinning works by associating a specific certificate or public key with a server, and checking that the certificate or key is being used when the app communicates with the server.

Public key pinning

Public key pinning, on the other hand, is a technique in which the client stores the public key of a trusted server and verifies that the public key presented by the server during the SSL/TLS handshake is the same as the stored public key. If the public key presented by the server does not match the stored public key, the connection is terminated.

Difference between Certificate pinning and Public key pinning

  • Both used to prevent man-in-the-middle (MITM) attacks, in which an attacker intercepts the communication between the client and the server and presents a fake certificate or public key in an attempt to impersonate the server.
  • Certificate pinning may be more effective in some cases, as it verifies the entire certificate chain and all of its associated trust relationships. Public key pinning, on the other hand, only verifies the public key of the server and does not take into account the trust relationships between the server and other certificate authorities.
  • Public key pinning might be more flexible which only required to check the public key which can be static. With certificate pinning, we need to replace certificates in the app every time when renew them.

Implement certificate pinning

To implement certificate pinning in Swift, you can use the URLSession class and the serverTrustPolicy property of the ServerTrustPolicy enum to configure the trust policy of the session, and the URLSessionDelegate protocol and the urlSession(_:didReceive:completionHandler:) method to evaluate the server trust. Here is an example of how to do this:

import Foundation

class PinningDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            let serverTrust = challenge.protectionSpace.serverTrust
            let certificate = SecTrustGetCertificateAtIndex(serverTrust!, 0)

            if let serverCertificateData = SecCertificateCopyData(certificate!) as Data? {
                // Compare the server certificate data with the trusted certificate data
                if serverCertificateData == trustedCertificateData {
                    let credential = URLCredential(trust: serverTrust!)
                    completionHandler(.useCredential, credential)
                    return
                }
            }
        }

        // Cancel the connection if the server certificate is not trusted
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

let delegate = PinningDelegate()

let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
let url = URL(string: "https://www.example.com")!
let task = session.dataTask(with: url) { data, response, error in
    // Handle the response here
}
task.resume()

In this example, the PinningDelegate class is a delegate of the URLSession class that implements the urlSession(_:didReceive:completionHandler:) method. The method is called when the session receives an authentication challenge from the server, and it is used to evaluate the server trust. The method retrieves the server trust and the server certificate from the challenge, and it compares the server certificate data with the trusted certificate data. If the server certificate is trusted, the method creates a URLCredential object with the server trust and returns it to the session, and the session continues the connection. If the server certificate is not trusted, the method cancels the connection and returns nil

Implement public key pinning

To implement certificate pinning in a Swift app, you can use the ServerTrustPolicyManager and ServerTrustPolicy classes from the Alamofire library. The ServerTrustPolicyManager class allows you to specify a set of trusted certificates or public keys, and the ServerTrustPolicy class allows you to specify the certificate pinning policy for a particular server.

Here is an example of how to use the ServerTrustPolicyManager and ServerTrustPolicy classes to pin a certificate in a Swift app:

import Alamofire

let certificateData = Data(base64Encoded: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")!
let certificate = SecCertificateCreateWithData(nil, certificateData as CFData)!
let trustedCertificates = [certificate]
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(certificates: trustedCertificates, validateCertificateChain: true, validateHost: true)
let serverTrustPolicyManager = ServerTrustPolicyManager(policies: ["example.com": serverTrustPolicy])

AF.Session(serverTrustPolicyManager: serverTrustPolicyManager).request("https://example.com").response { response in
    // Handle the response
}

In this example, we use the SecCertificateCreateWithData function to create a SecCertificate object from the certificate data, and add it to an array of trusted certificates. We then use the ServerTrustPolicy.pinCertificates method to create a ServerTrustPolicy object that trusts the specified certificates, and validate the certificate chain and host. We then create a ServerTrustPolicyManager with the ServerTrustPolicy just created.

Further read

Certificate pinning and Public key pinning in Kotlin.