I want to secure my app against man-in-the-middle (mitm) attacks using SSL Pinning.
By default it is possible to use a proxy like Charles or mitmproxy to intercept traffic, and decrypt it using a self-signed certificate.
After extensive research, I found several options:
Adding
NSPinnedDomains > MY_DOMAIN > NSPinnedLeafIdentities
toInfo.plist
Apple Documentation: Identity Pinning
Guardsquare: Leveraging Info.plist Based Certificate Pinning
Pros: Simple
Cons: App becomes unusable once Certificate/Private Key is renewed (typically after a few months)Adding
NSPinnedDomains > MY_DOMAIN > NSPinnedCAIdentities
toInfo.plist
Apple Documentation: same as above
Pros: Simple. No failure on Leaf Certificate renewal because Root CAs are pinned instead (expiration dates decades out)
Cons: Seems redundant as most root CAs are already included in the OSChecking certificates in code
URLSessionDelegate
>SecTrustEvaluateWithError
(or Alamofire wrapper)
Ray Wenderlich: Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning
Apple Documentation: Handling an Authentication Challenge
Medium article: Everything you need to know about SSL Pinning
Medium article: Securing iOS Applications with SSL Pinning
Pros: More flexibility. Potentially more secure. Recommended by Apple (see Apple-link above).
Cons: A more laborious version of (1) or (2). Same Cons as (1) and (2) regarding leaf expirations / root CA redundancies. More complicated.Adding NSExceptionDomains > MY_DOMAIN > NSRequiresCertificateTransparency to Info.plist
Apple documentation: Section Info.plist keys 'Certificate Transparency'
Pros: Very simple. No redundant CA integration.
Cons: Documentation is unclear whether this should be used for ssl pinning
After evaluation I came to the following conclusion:
- Not suitable for a production app because of certificate expiration
- Probably best balance between simplicity, security and sustainability – but I don't like the duplication of adding root CAs the system already knows
- Too complicated, too risky, any implementation error may lock the app
- My preferred way. Simple. Works in my tests but – unclear documentation.
I am tempted to use option (4) but I am not sure if this is really meant for ssl pinning.
In the documentation it says:
Certificate Transparency (CT) is a protocol that ATS can use to identify mistakenly or maliciously issued X.509 certificates. Set the value for the NSRequiresCertificateTransparency key to YES to require that for a given domain, server certificates are supported by valid, signed CT timestamps from at least two CT logs trusted by Apple. For more information about Certificate Transparency, see RFC6962.
and in the linked RFC6962:
This document describes an experimental protocol for publicly logging the existence of Transport Layer Security (TLS) certificates [...]
The terms "experimental protocol" and "publicly logging" raise flags for me and although flipping the feature on in the Info.plist seems to solve SSL pinning I am not sure if I should use it.
I am by no means a security expert and I need a dead simple solution that gives me decent protection while protecting me from choking my own app through possible expired / changed certificates.
My question:
Should I use NSRequiresCertificateTransparency
for ssl pinning and preventing mitm-attacks on my app?
And if not:
What should I use instead?
PS:
This same question was essentially already asked in this thread:
https://developer.apple.com/forums/thread/675791
However the answer was vague about NSRequiresCertificateTransparency
(4. in my list above):
Right, Certificate Transparency is great tool for verifying that a provided leaf does contain a set of SCTs (Signed Certificate Timestamp) either embedded in the certificate (RFC 6962), through a TLS extension (which can be seen in a Packet Trace), or by checking the OCSP logs of the certificate. When you make a trust decision in your app, I would recommend taking a look at is property via the SecPolicyRef object.
Additional side note:
My expectation from Apple as a security-aware company would have been, that pinning to root CAs was enabled by default, and that I would have to add exceptions manually, e.g. allow proxying with Charles on debug builds. I hear Android does it that way.