3

I'm using Ktor and Kotlin/native in iOS in an iOS app that accesses an internal dev server. The dev server uses a certificate issued by an internal CA which is not publicly trusted.

When trying to access the server with the following code :

  internal suspend fun performHttp(url : String)
    {
        // URL is a self signed HTTPS: request
        val client = HttpClient(Ios) 

        val response = client.get<String>(url)
        println(response)
    }

it throws the following exception :

TIC SSL Trust Error [32:0x281956dc0]: 3:0
esri2[470:136341] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9807)
esri2[470:136341] Task <F3CC4C40-0231-4E58-97F3-F457D5A18BB0>.<1> HTTP load failed (error code: -1202 [3:-9807])
 esri2[470:136417] Task <F3CC4C40-0231-4E58-97F3-F457D5A18BB0>.<1> finished with error - code: -1202
esri2[470:136211] Task <F3CC4C40-0231-4E58-97F3-F457D5A18BB0>.<1> load failed with error Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “server1.internal.lan” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
    "<cert(0x12b094e00) s: server1.internal.lan i: Internal-Issuing-CA2>",

How do I convince Ktor that it should access this URL, or ignore untrusted certs? Yes, I know that one should not ignore untrusted certs, but this is a lab test.

sandpat
  • 1,478
  • 12
  • 30
Trond Tunheim
  • 115
  • 10
  • Check out this, might do the trick for you: https://stackoverflow.com/a/40299837/5147552 – Aleksandr Honcharov Jan 31 '20 at 16:03
  • I had already tried that, I did not mention that in my post. I have added the domain to NSExceptionDomains list with NSIncludesSubdomains = true and NSExceptionAllowsInsecureHTTPLoads = true. It doesn't look like Ktor is using that config. The workaround so far was to export the internal root CA as a pem file , and email it to my iPhone ,install it and trust it (https://medium.com/collaborne-engineering/self-signed-certificates-in-ios-apps-ff489bf8b96e). I hope there is a better solution to this in Ktor. – Trond Tunheim Feb 03 '20 at 10:16
  • Hi, did you find a real solution? – Thomas Vos Feb 18 '20 at 15:20
  • No I haven't. I'm still hoping there are some better solution to this problem – Trond Tunheim Feb 19 '20 at 13:57

1 Answers1

2

Ktor iOS engine offers the ability to configure the underlying NSURLSession with the help of IosClientEngineConfig.kt.

With it you can configure (amongst other things) a ChallengeHandler by setting the block for handleChallenge in the config like this:

val client = HttpClient(Ios) {
    engine {
        handleChallenge(TrustSelfSignedCertificate())
    }
}

Then you need to implement a class in Kotlin something like this:

internal data class TrustSelfSignedCertificate internal constructor(
    private val validateTrust: Boolean = true
) : ChallengeHandler {

    override fun invoke(
        session: NSURLSession,
        task: NSURLSessionTask,
        challenge: NSURLAuthenticationChallenge,
        completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit
    ) {
        val hostname = challenge.protectionSpace.host

        val serverTrust = challenge.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("myOwnCert", "cer")

        val localCertificate: NSData = NSData.dataWithContentsOfFile(pathToCert!!)!!

        if (localCertificate == cert1) {
            completionHandler(
                NSURLSessionAuthChallengeUseCredential,
                NSURLCredential.create(serverTrust)
            )
        } else {
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, null)
        }
    }
}

Also, don't forget to put you certificate as a file "myOwnCert.cer" into you iOS project (maybe on the top-level).


NOTE

Ktor with iOS engine does not respect/use NSApptransportSecurity.


The code is based on this answers.

With the help of this blog-post.

Jens
  • 6,243
  • 1
  • 49
  • 79