Kotlin Multiplatform iOS: Certificate Pinning
How to implement certificate pinning in Kotlin Multiplatform using Ktor
Certificate Pinning
Pinning certificates is a common practice when interacting with remote APIs. It is the act of constraining which certificates you trust. This helps to defend against attacks on certificate authorities. It also helps combat man-in-the-middle attacks.
There is a multitude of information available about certificate pinning. Here is some to get you started:
- https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning
- https://labs.nettitude.com/tutorials/tls-certificate-pinning-101/
- https://docs.broadcom.com/docs/certificate-pinning-en
Kotlin Multiplatform
Kotlin Multiplatform (KMP) is a way of writing cross-platform code in Kotlin. It is not about compiling all code for all platforms, and it doesn’t limit you to a subset of common APIs. KMP provides mechanisms for writing platform-specific implementations for a common API.
Ktor
Ktor is a KMP framework for building asynchronous servers and clients. Ktor’s client libraries enable you to author multiplatform code which connects to remote APIs.
Ktor client supports JVM, Android, iOS, Javascript, macOS, Linux, and Windows.
Ktor provides engines that perform the platform-specific requests. These engines conform to a common interface. In common code, you can make HTTP requests which go through the engines.
For Android, you can use an OkHttp engine. Built into Ktor there is an iOS engine that uses the NSURLSession
classes.
Pinning with OkHttp
OkHttp has long supported certificate pinning against public keys. Implementation is simple, and I love their approach to this problem.
Pinning with iOS
On iOS, this is not so simple. You have to override a function on NSURLSessionDataDelegateProtocol
and configure an NSURLSession
with it.
Represented in Kotlin:
fun invoke(
session: NSURLSession,
challenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit
)
Within this function, you need to check various aspects of the challenge. Ultimately checking the certificate matches your expectations.
CertificatePinner
I set out to write this certificate pinning functionality into a succinct class for KMP. Since we need to be able to override a delegate method, we are required to use at least version 1.3.2 of Ktor.
This is the class I came up with. It is inspired by OkHttp’s CertificatePinner
and matches pretty closely with their implementation.
How to use
To get started, configure certificate pinning with a broken configuration. Then read the expected configuration in the logs when the connection fails. Be sure to do this on a trusted network, and without man-in-the-middle tools like Charles or Fiddler.
For example, to pin https://publicobject.com, start with a broken configuration:
As expected, this fails with an exception, see the logs:
Now paste the public key hashes into the certificate pinner’s configuration:
Under the hood
ChallengeHandler
The first thing to note is that CertificatePinner
implements ChallengeHandler
. This means it implements the required delegate method. It will be called within the iOS engine.
This Builder
and Pin
classes enable the user to easily create a set of public keys to pin against. They also provide wildcard functionality. You can read about this in the class documentation.
NSURLSession
Delegate
The invoke
function is where the actual pinning functionality takes place.
First I retrieve the hostname
from the challenge
. Then I check to see if there are any pins against that host. I then check to see if the authenticationMethod
is suitable for certificate pinning.
See apple documentation for details.
I retrieve the serverTrust
and check it’s validity (if configured to).
Now comes the point where I check the certificates public keys against the ones that are pinned. Most of the complexity here is retrieving the public key in the format I want.
Once in the correct format, we check it matches the pinned ones and return the result.
Conclusion
This was a challenging problem to solve. It involved using Kotlin’s C-interop features, which can be difficult to grasp.
This class is available for you to use and change as you like. Hopefully, it helps you to solve this problem too.
Previous Post
Originally published at https://www.brightec.co.uk.