SSL Certificate Pinning on iOS Using TrustKit
With the proliferation of mobile and wearable tech, the security of these devices has never been more important. Learn how to better secure your iOS code.
Join the DZone community and get the full member experience.
Join For FreeIn past posts, I covered why certificate pinning in mobile apps is important and illustrated how to implement it both in iOS and Android. For the sake of simplicity, we had to omit a lot of nasty details and corner cases from our example. In the real world, the code might get complex very quickly, when you have to support various legacy iOS platforms, various popular networking libraries out there, etc. Luckily, the nice folks at DataTheorem have created and open-sourced a framework for SSL pinning that simplifies most of this process. The framework, called TrustKit, makes it very easy to integrate pinning into your mobile application. In the following tutorial, we’ll show you how to use it on iOS.
Installing the Framework
It is trivial to install the TrustKit framework using CocoaPods.
Just run the following to your Podfile:
pod 'TrustKit'
and run:
pod install
Alternatively, TrustKit is available through Carthage as well.
Add the following line to your Cartfile:
github "datatheorem/TrustKit"
and run:
carthage build --platform iOS
Preparing the Pins
As we explained in our introduction tutorial, you can pin either the certificate itself or the public key from within the certificate. The latter is preferred, since on many occasions, for security reasons, the certificates are being rotated regularly and we will have to redeploy our application with the new certificate, with a risk of some users getting locked out. We’ve also shown that it is better for the app to store just the hash of the public key, not the key itself. TrustKit takes the same approach. For each domain, it expects to have a list of hashes of public keys to pin. It does, however, expect them in a base64 encoded format, so, for reasons of convenience, a special tool is provided within the TrustKit package to help prepare them.
As with the previous example, we will use www.google.com as an example. Let's first extract the certificate from the domain:
openssl s_client -connect www.google.com:443 -showcerts < /dev/null | openssl x509 -outform DER > google.der
Now, let's run the provided helper script to extract the hash of the public key:
|
In our example, we are using a binary DER format when working with certificates. You have your certificate provided to you in a different text PEM format. In that case, simply omit the –type DER part and call the script on your PEM file directly.
The result is going to be the following:
CERTIFICATE INFO
----------------
subject= /C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
issuer= /C=US/O=Google Inc/CN=Google Internet Authority G2
SHA1 Fingerprint=FB:BD:7E:87:A8:A6:EB:FC:3E:E0:BE:F8:2E:3A:EB:9C:7D:86:7F:AA
TRUSTKIT CONFIGURATION
----------------------
kTSKPublicKeyHashes: @[@"U9GGUC1+PGI7SLb8XVVuuBPLkQKbQ2LnfU5Wfe7WAAg="] // You will also need to configure a backup pin
kTSKPublicKeyAlgorithms: @[kTSKAlgorithmRsa2048]
Note that the output of the script has both the hash and the algorithm used. This is, in fact, a nice touch. TrustKit supports several algorithms and will auto-detect and handle it automatically for us. Notice the output of that script is even giving us Objective-C elements so we can copy-paste them into our code directly. Unfortunately, we will be using Swift for our example, but TrustKit gets extra points for this.
Initializing TrustKit
Now it's time to initialize the framework:
let trustKitConfig = [
kTSKSwizzleNetworkDelegates: false,
kTSKPinnedDomains: [
"google.com": [
kTSKIncludeSubdomains : true,
kTSKPublicKeyAlgorithms: [kTSKAlgorithmRsa2048],
kTSKPublicKeyHashes: [
"U9GGUC1+PGI7SLb8XVVuuBPLkQKbQ2LnfU5Wfe7WAAg=",
"WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="
],]]] as [String : Any]
TrustKit.initialize(withConfiguration: trustKitConfig)
The pinned domains is an array, so we can actually pin several different domains in our application, each with a specific set of hashes and settings.
Why do we have 2 hashes in the hashes list? TrustKit will actually fail and not let you start with one. The idea is you have to create a backup key, so in case your primary gets compromised, you will have a way to replace the certificates on your server without locking out your users.
Validating Secure Connections
Now that everything is set up, we are ready to use the TrustKit mechanism to actually validate each and every connection our application is going to open. In order to do this, we will make sure our URLSession delegate implements the didReceive challenge method:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
// Let TrustKit handle it
TSKPinningValidator.handle(challenge, completionHandler: completionHandler)
}
That's it, we're done! Every time our URLSession will try to establish an SSL connection to google.com or one of its subdomains, TrustKit will make sure to extract the certificate and the public key, hash the key and compare it to the list we’ve passed during initialization. And it will work on all iOS/watchOS/tvOS, and macOS versions.
Published at DZone with permission of Dmitry Fink. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments