I have a PEM file of the ISRG Root X1 certificate which I downloaded from https://letsencrypt.org/certificates/
and I’m trying to implement certificate pinning in my iOS app. I’m specifically interested in public key pinning and I’m targeting iOS 12 and above.
I have two main questions:
-
How can I generate a SHA256 hex string from the PEM file?
-
Once I have the SHA256 hex string, how can I implement root certificate public key pinning in Swift using URLSession, without relying on any external libraries?
I would greatly appreciate any assistance or resources that could shed light on this matter. Thank you in advance!
— Edited
According to what I found on StackOverflow and other sources, the SHA-256 hex string I generated using OpenSSL differs from the one I obtained in the code during TLS connections.
Command used:
openssl rsa -pubin -inform PEM -outform DER -in public_key.pem | openssl enc -base64
Question – why it is different is it expected?
6
I usually have a slightly different approach that i will share to see if it helps.
- I download the certificate already in CER format
In a terminal i simply run this command:
openssl s_client -connect <URL> -servername <server-name> < /dev/null | openssl x509 -outform DER > <output-file-name>.cer
Since it seems you already have the PEM file, you can run this command:
openssl x509 -inform PEM -outform DER -in <your-file-name>.crt -out <output-file-name>.cer
-
You have to include the certificate file with extension CER in your project.
-
Implementation of certificate pinning
Initialise your URLSession with delegate
URLSession(
configuration: sessionConfiguration,
delegate: self,
delegateQueue: nil
)
Then you simply implement this function:
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
guard
let serverTrust = challenge.protectionSpace.serverTrust,
let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0)
else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let certificatesURLs = Bundle.main.urls(forResourcesWithExtension: "cer", subdirectory: nil)
let certificatesData = certificatesURLs?.compactMap { try? Data(contentsOf: $0) }
// Compare the server's certificate with the pinned ones
let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data
if certificatesData?.contains(serverCertificateData) == true {
// Found a certificate that matches, allow the connection
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else {
// Didn't found a certificate that matches, cancel the connection
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
This will allow you to have multiple certificates installed, it can be usefully when one is about to expire but you already have the newer one available, so when one expires, the other one will still be validated.