2

I'm trying to communicate with a SSL secured server (https) in a local network which uses a Self-Signed Certificate Authority to generate certificates. I'm using okhttp-tls to create SSLSocketFactory and TrustManager (As per the OkHttp recipes: CustomTrust).

Relevant code snippet is as follows:

val certificateFactory = CertificateFactory.getInstance("X.509")
val certificate = certificateFactory.generateCertificate(caFileInputStream) as X509Certificate
val certificates = HandshakeCertificates.Builder()
    .addTrustedCertificate(certificate)
    .build()
val client = OkHttpClient.Builder()
            .sslSocketFactory(sslSocketFactory, trustManager)
            .addInterceptor(loggingInterceptor)
            .dns(dns)
            .build()

I'm also using custom Dns implementation to resolve the domain name from the request url to our custom IP address.

As a flaky test, I'm calling showUrl method from the same okhttp sample for one of our valid REST endpoints.

The test-case runs successfully on the JVM (unit-test) but fails on Android (androidTest) with CertPathValidatorException: Trust anchor for certification path not found Here is the entire stack-trace

2021-12-21 15:43:21.951 11338-11338/com.kshitijpatil.sslsandbox E/ShowURL: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at com.android.org.conscrypt.SSLUtils.toSSLHandshakeException(SSLUtils.java:363)
        at com.android.org.conscrypt.ConscryptEngine.convertException(ConscryptEngine.java:1134)
        at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1089)
        at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:876)
        at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:747)
        at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:712)
        at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:858)
        at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.access$100(ConscryptEngineSocket.java:731)
        at com.android.org.conscrypt.ConscryptEngineSocket.doHandshake(ConscryptEngineSocket.java:241)
        at com.android.org.conscrypt.ConscryptEngineSocket.startHandshake(ConscryptEngineSocket.java:220)
        at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:379)
        at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:337)
        at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:209)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:226)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
        at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:221)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
        at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
        at com.kshitijpatil.sslsandbox.NetworkService.showUrl(NetworkService.kt:169)
        at com.kshitijpatil.sslsandbox.MainActivity$onCreate$1$1$1$1.invokeSuspend(MainActivity.kt:24)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:39)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
     Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:672)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:549)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:505)
2021-12-21 15:43:21.956 11338-11338/com.kshitijpatil.sslsandbox E/ShowURL:     at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:425)
        at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:353)
        at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
        at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:90)
        at com.android.org.conscrypt.ConscryptEngineSocket$2.checkServerTrusted(ConscryptEngineSocket.java:163)
        at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:255)
        at com.android.org.conscrypt.ConscryptEngine.verifyCertificateChain(ConscryptEngine.java:1638)
        at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
        at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:569)
        at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1095)
        at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1079)
            ... 36 more
     Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            ... 50 more

I'm also using the following network security config and have correctly configured it in the AndroidManifest.xml

<?xml version="1.0" encoding="UTF-8" ?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="user"/>
            <certificates src="system"/>
            <certificates src="@raw/<custom_ca_path>" />
        </trust-anchors>
    </base-config>
</network-security-config>

I believe the issue is related to some platform-specific SocketFactory implementation of OkHttp based on the fact that, unit-test is passing but androidTest is failing. Any help would be really appreciated.

EDIT:

Writing a custom TrustManager as follows somehow solves the issue. I'm not sure whether it's a good enough solution.

val certificate = certificateFactory.generateCertificate(caFileInputStream) as X509Certificate
val trustManager = object : X509TrustManager {
            @Throws(CertificateException::class)
            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
            }

            @Throws(CertificateException::class)
            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
                chain.forEach {
                    it.checkValidity()
                    try {
                        it.verify(certificate.publicKey)
                    } catch (e: Exception) {
                        e.printStackTrace()
                        throw CertificateException(e.message)
                    }
                }
            }

            override fun getAcceptedIssuers(): Array<X509Certificate> {
                return emptyArray()
            }
        }
Kshitij Patil
  • 132
  • 1
  • 9
  • Deleted my answer, since it wasn't addressing your question correctly. If it's working for a test server, then you may want to leave it like this. Make sure you don't use this code in production. I'll try to reproduce this problem with a self signed certificate and network-security-config. – Yuri Schimke Dec 21 '21 at 11:33
  • Okay @YuriSchimke, thanks for your quick response! Looking forward to your findings. – Kshitij Patil Dec 21 '21 at 12:38
  • Don't block on me, you seem to have a workaround, after I confirm it's working or not, I'll let you know. May not be immediate though. – Yuri Schimke Dec 21 '21 at 14:55
  • Sure, we're moving ahead with the workaround as a temporary solution. – Kshitij Patil Dec 22 '21 at 05:12
  • @YuriSchimke have you managed to reproduce the problem? Should I prepare a reproducible set up and open an issue on okhttp repository? – Kshitij Patil Dec 30 '21 at 10:08
  • One thing with your code, with `.addTrustedCertificate(certificate)` you won't have the platform configured cas included. So you can either use the network-security-config and not configure HandshakeCertificates. – Yuri Schimke Dec 31 '21 at 15:50

1 Answers1

0

This example shows it working with either a custom ca in network-security-config or client configured with the trusted certificate.

https://github.com/yschimke/okhttp/blob/832132be3b10b76ec645b99e8f4173ae09d3beb3/android-test/src/androidTest/java/okhttp/android/test/OkHttpSelfSignedTest.kt

HandshakeCertificates:

  val certificate = """
      -----BEGIN CERTIFICATE-----
      MIIBMTCB2KADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMMCWxvY2FsaG9z
      dDAeFw0yMTEyMjMwNTQ5MTBaFw0yMTEyMjQwNTQ5MTBaMBQxEjAQBgNVBAMMCWxv
      Y2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABORfR8tCugvrBb3aWRPY
      NsNhEYWMhld41tDaQdUkIs8tKKdgdbJd1XSbITotW2WsgQLqkplZp1Eqabg6ezIn
      ka2jGzAZMBcGA1UdEQEB/wQNMAuCCWxvY2FsaG9zdDAKBggqhkjOPQQDAgNIADBF
      AiEA0Zw+tLJD017M1gM7dQeNolxk7YffV9LuizrUV9meUIMCIFNj64gbRDmK0Yps
      qJH4bEVX0U8lbj1UD852nDGud7ZY
      -----END CERTIFICATE-----
  """.trimIndent()

    val trustedCert = certificate.decodeCertificatePem()

    val clientCertificates = HandshakeCertificates.Builder()
      .addTrustedCertificate(trustedCert)
      .build()

    client = client.newBuilder()
      .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
      .build()
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69