SSL pinning or Certificate Pinning

Mahesh Asabe
3 min readMar 3, 2021

Client-server communication happens using web services or API calls, Consider client invokes API request and asking for a response from the intended server & the man-in-the-middle attack scenario, where the client receives a response from any other server rather than the intended server. In such cases, the client needs to identify which server is giving a response! This can be done with help of SSL pining or certificate pinning.

A man-in-the-middle attack simply achieved using Charles Proxy tools, This tool is actually built for API debugging purposes but can be misused as well as I explained.

Let us talk about the solution which we call SSL pinning or Certificate Pinning!!!

SSL stands for Secure Socket Layer, this protocol which helps in authentication, encryption-decryption of data sent over the internet.

While SSL certificate is a data file that needs to installed on the server so that it allows a secure connection between client and server. Nowadays, SSL certificates also support Transport Layer Security.

The below image explains a lot about the certificate. It has a serial number, issuer, validity, public key, and signature.

Let us talk about, how SSL pinning or certificate happens

In SSL pinning, the client keeps certificate copies locally and does match with server certificate at each API call. If match found means we received a response from the intended server, otherwise abort the connection.

Implementation of SSL pinning in Swift :

1.Import Security framework

2.Evaluation or Match did in NSURLSession callback method

class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void {
// This the callback where certificate pinning should be done
}

3.Get SecTrust from the URLAuthenticationChallenge.

4.Evaluate SecTrust to check for a valid certificate

func doCertificatePinning(challenge: URLAuthenticationChallenge) {if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {

if let serverTrust = challenge.protectionSpace.serverTrust {
let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil)
if(isServerTrusted) {
matchCertificate(serverTrust)
}
}
}
}

5.First, read the certificate from the local bundle.

6.Match local certificate with Server certificate (Server pinning)

func matchCertificate(serverTrust: SecTrust) {if let serverCertificate=SecTrustGetCertificateAtIndex(serverTrust, 0) {

let serverCertificateData = SecCertificateCopyData(serverCertificate)

let data = CFDataGetBytePtr(serverCertificateData);

let size = CFDataGetLength(serverCertificateData);

let serverCertificateData = NSData(bytes: data, length: size)

let localCertificate = Bundle.main.path(forResource: "certificateFile", ofType: "der")

if let file = localCertificate {

if let localCertificateData = NSData(contentsOfFile: file) {

if serverCertificateData.isEqual(to: localCertificateData as Data) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))

return
}
}
}
}
}

7.Match local public key with the public key from server certificate from certificates. (Public key pinning)

The below function helps to get the public key from the certificate. Get public keys for both local and server certificates and match them.

private func publicKey(for certificate: SecCertificate) -> SecKey? {      var publicKey: SecKey?     
var trust: SecTrust?
let trustCreationStatus = SecTrustCreateWithCertificates(certificate, SecPolicyCreateBasicX509(), &trust)
if let trust = trust, trustCreationStatus == errSecSuccess { publicKey = SecTrustCopyPublicKey(trust)
}
return publicKey
}

8. Abort connection

// When Pinning failed
completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)

--

--