Closed Rjmaurya13 closed 4 years ago
Any suggestions for a workaround for self-signed certificate handling on iOS?
I recently had to tackle implementing iOS certificate pinning. Here is a blog post I just published detailing my solution. Feel free to use it to help you work out your solution. Hope it helps someone.
https://medium.com/@alistairsykes/kotlin-multiplatform-ios-certificate-pinning-fd1abba5ca8f
When I get it right, I need to add your CertificatePinner
and use it like so:
HttpClient(Ios) {
engine {
challengeHandler = CertificatePinner.Builder()
.add("publicobject.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
}
}
This would be great and easy to use but when coping the class from your article, the imports are missing and there are many unresolved references.
You're mentioning that a fix for our workaround will be available with f992dd3cad59d8d4262c0c924c59154ce03c0181 which is now merged in ktor 1.3.2.
@alistairsykes Would you please update your article with an example how to add certificate pinning for iOS using this new version?
I have update my blog @hardysim to better reflect using this with version 1.3.2.
The api changed slightly since writing my blog and 1.3.2 release. The implementation will now look more like this:
HttpClient(Ios) {
// ...
engine {
val builder = CertificatePinner.Builder()
.add("publicobject.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
handleChallenge(builder.build())
}
}
Not sure which imports you'd be missing. Recommend checking out: https://ktor.io/clients/http-client/quick-start/client.html https://ktor.io/clients/http-client/multiplatform.html
Maybe you are looking for:
import io.ktor.client.HttpClient
import io.ktor.client.engine.ios.Ios
In terms of imports for the CertificatePinner itself, it depends where you copy this class to, and what package name you use. You will see in my blog that I have set package as package com.example.util
, but recommend you package it wherever best suits your project.
Also I found this, really helpful when it comes to imports.
Hope that was helpful, shout if you'd like more information on my post.
The blog post seems not to be updated. At least, the signature of invoke()
does not match the one from ktor 1.3.2. Maybe you can post the class here as well?
The imports for are missing for toNSData()
and SecCertificateRef.getPublicKeyBytes()
complains about the wrong return type (requires ByteArray?
but found Unit
). Maybe because toByteArray()
is missing as well?!
Sorry about that @hardysim . Didn't update the gist correctly.
Updated main gist:
invoke()
updated to the correct signatureUpdated cutils gist:
toNSData()
and toByteArray()
Try again. Really sorry about that.
Yeah, getting closer 🎉.
Still missing imports for @VisibleForTesting
(but I can just remove that for now), NSMutableData
and ByteVar
.
And I get two errors on base64EncodedStringWithOptions()
:
The integer literal does not conform to the expected type NSDataBase64EncodingOptions / = ULong /
I can use import platform.Foundation.NSMutableData
but it's still missing appendBytes()
and reinterpret()
.
@VisibleForTesting
annotation and set functions to private.I'm not seeing the base64EncodedStringWithOptions()
error. Could this be due to the missing imports.
Hopefully that resolves it. Sorry again @hardysim.
Hi @alistairsykes, could you make the PR with the CertificatePinner
? It would be nice to support it out of the box.
@alistairsykes it seems to compile now 👍 But I still get those 2 errors:
And yes, a PR would be great.
Hi @alistairsykes, could you make the PR with the
CertificatePinner
? It would be nice to support it out of the box.
I'd love to.
@alistairsykes Thanks for providing this implementation. Unfortunately, I see the same error on base64EncodedStringWithOptions()
as @hardysim.
Additionally, when setting up the CertificatePinner
for initial failure, the request fails as expected, but with "Server trust is invalid", which does not give the certificate chain to be used. This happens on the ios simulator, if it makes a difference?!
Could you try using 0.convert()
?. It looks like the type is platform dependent.
I included a fun on the builder validateTrust
which might help with the emulator issue.
@e5l thanks, this works! Beforehand I had used 0u
.
@alistairsykes this actually helps as well, but only partially: validateTrust
does the trick I receive
HttpClient: Certificate pinning failure!
Peer certificate chain:
sha256/UIUWcLlepHcfOtECdRs0Hzrnu8QpOsZkFHXNOP9t2vw=: *.cognitive.microsoft.com
Pinned certificates for text-recognizer.cognitiveservices.azure.com:
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
but when I replace sha256/AAA...
with the above sha256/UIUW...
, it looks to me like I am back to square one without the CertificatePinner:
Exception in http request: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “text-recognizer.cognitiveservices.azure.com” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
"<cert(0x7fb49303aa00) s: *.cognitive.microsoft.com i: Microsoft IT TLS CA 5>",
"<cert(0x7fb493034200) s: Microsoft IT TLS CA 5 i: Baltimore CyberTrust Root>"
), NSErrorClientCertificateStateKey=0,
Any chance you have a suggestion?
@alistairsykes I finally got around to test this on an actual iPhone and it looks like this is only an issue on the ios simulator?! Just wanted to let you know. Thanks for the advice and the great work
Here is the code that worked for me. https://stackoverflow.com/questions/58777854/ktor-multiplatform-ssl-pinning-for-ios-in-kotlin
I hope it will help.
override fun URLSession(
session: NSURLSession,
didReceiveChallenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit) {
val serverTrust = didReceiveChallenge.protectionSpace.serverTrust
var result: SecTrustResultType = 0u
memScoped{
val nativeResult = alloc<SecTrustResultTypeVar>()
nativeResult.value = result
SecTrustEvaluate(serverTrust!!, nativeResult.ptr)
}
val serverCertificate = SecTrustGetCertificateAtIndex(serverTrust,0)
val serverCertificateData = SecCertificateCopyData(serverCertificate)
val data = CFDataGetBytePtr(serverCertificateData)
val size = CFDataGetLength(serverCertificateData)
val cert1 = NSData.dataWithBytes(data,size.toULong())
val pathToCert = NSBundle.mainBundle.pathForResource("Your Certificate","cer")
val localCertificate : NSData = NSData.dataWithContentsOfFile(pathToCert!!)!!
if (localCertificate == cert1) {
completionHandler(NSURLSessionAuthChallengeUseCredential,NSURLCredential.create(serverTrust))
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, null)
}
}
@alistairsykes is there a way to perform pinning only for the root and intermediate certificate? Based on tries I can see that I must perform pinning in the whole certificate chain in order to get a succesful response.
After the fix, you need to pin only a single certificate in the chain
@e5l in which version I can find the fix? I suppose 1.5.2 ?
Sure, 1.5.2
on the last week of Feb
@e5l is there an alpha version I can test;
Sure, the latest EAP 22: https://ktor.io/eap/
How to make SSL pinning for iOS. As I am getting response but for other thing its not working.
Getting crash near this code
val remoteCertificateData : NSData = SecCertificateCopyData(certificate) as NSData
This is the error.
Here is my code.