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.