Kotlin Multiplatform iOS: Certificate Pinning

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.

https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc

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.

Builder

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.

Interested in more Kotlin?

Check out Alistair's other musings on all things Kotlin:

- Kotlin Multiplatform Android/iOS: Testing

- Kotlin Multiplatform Android/iOS: Project Structure Strategies

- Kotlin Multiplatform Android/iOS: Coding and culture

- Or if you're completely new to Kotlin, check out our overview, Kotlin is here. So what?


Looking for something else?

Search over 400 blog posts from our team

Want to hear more?

Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!