kittinunf / fuel

The easiest HTTP networking library for Kotlin/Android
https://fuel.gitbook.io/documentation/
MIT License
4.55k stars 431 forks source link

Fuel fails to validate server certificates even after the CA is imported into the truststore #782

Open david-sackstein opened 3 years ago

david-sackstein commented 3 years ago

Bug Report

Description

I am writing a client for a web site that supports one-sided SSL (TLS).

That is, the site does not require that my client presents a certificate to authenticate itself, but it does provide a certificate to enable my client to authenticate the site.

This requires that the certificate presented by the server is signed by a certificate that has been imported into a trust store on the client machine.

Documentation that I have read claims that Java client libraries look for the trust store at $JAVA_HOME/jre/lib/security/cacerts.

This does not seem to working with the Fuel library.

To Reproduce

  1. Create a SSL web site with a self signed certificate.
  2. Import the certificate into the JAVA default truststore like so: keytool -trustcacerts -keystore "$JAVA_HOME/jre/lib/security/cacerts" -storepass changeit -importcert -file "server.crt" and verify that keytool reports that the certificate was successfully imported.
  3. Reboot the machine to make sure that the JVM is restarted and picks up the change.
  4. Create a client using the Fuel kotlin library and make a call to the site over SSL (https)
  5. The call fails with an exception: PKIX path building failed ... unable to find valid certification path to requested target
  6. The following code for the client attempts to explicitly load the truststore into a FuelManager instance, but using that instance still results in the same error:
private fun createFuelManager(): FuelManager {
   return FuelManager().apply {
     val ks: KeyStore = KeyStore.getInstance("JKS")
     ks.load(FileInputStream(<path to truststore.jks>), "changeit".toCharArray())

     // sets the keystore field of the new FuelManager

     keystore = ks 
  }
}

Expected behavior

The call over https should return with code 200

Environment

Additional context

A reasonable solution for my problem would be to allow external configuration or configuration through code to configure the location of the truststore that the library will look in for validating server certificates.

david-sackstein commented 3 years ago

I have also posted a question on stack overflow on this matter: https://stackoverflow.com/questions/64807755/how-can-i-configure-the-path-to-a-truststore-when-using-the-fuel-kotlin-library

tomholub commented 3 years ago

I did successfully configure my own truststore, in Kotlin on OpenJVM 11, this way:

FuelManager.instance.keystore = trustStore

After that, running Fuel.post(...) indeed uses this truststore and stops failing.

david-sackstein commented 3 years ago

Thank you! Would you mind also sharing the code that sets up the custom truststore? Thanks again

Le ven. 4 déc. 2020 à 10:27, Tom J notifications@github.com a écrit :

I did successfully configure my own truststore, in Kotlin on OpenJVM 11, this way:

FuelManager.instance.keystore = trustStore

After that, running Fuel.post(...) indeed uses this truststore and stops failing.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/kittinunf/fuel/issues/782#issuecomment-738641234, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADRZGYA724HX7KAYDPBCWZ3STCMOXANCNFSM4TVFZPAA .

tomholub commented 3 years ago

Very roughly - refactored to remove custom stuff but not re-tested. You pass it path to the trust store file.

    fun getKeyStore(filePath: String, pass: CharArray): KeyStore {
            val file = File(filePath)
            if (!file.exists()) throw FileNotFoundException(filePath)
            val keyStore = KeyStore.getInstance("PKCS12")
            keyStore.load(FileInputStream(file), pass)
            return keyStore
    }
kittinunf commented 3 years ago

Do you think we need an action from the library's (Fuel) side then?

tomholub commented 3 years ago

I'd say this could be documented somewhere, it's a common usecase in larger deployments to use custom certs.

On Sat, 5 Dec 2020, 13:57 Kittinun Vantasin, notifications@github.com wrote:

Do you think we need an action from the library's (Fuel) side then?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/kittinunf/fuel/issues/782#issuecomment-739247695, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQDZEPSZLBNH4PBLHYVAOTSTIU4RANCNFSM4TVFZPAA .

kittinunf commented 3 years ago

Good idea. Alrighty, then I will open a PR and ask for your review if you don't mind. Thanks!

vojkny commented 2 years ago

Is this now documented somewhere? I am having the same issue with:

    val fuel = FuelManager().also {
        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        keyStore.load(this::class.java.classLoader.getResourceAsStream(keyStoreCertificate), keystorePassword.toCharArray())
        it.keystore = keyStore
    }

I don't want to use:

FuelManager.instance.keystore = trustStore

as that would influence other fuel calls.

EDIT1: I tried the FuelManager.instance.keystore and it does not work for me either.

EDIT2: I needed to pass password to the SocketFactory with:

        …
        val sslContext = SSLContext.getInstance("TLS")
        val keyMngFactory = KeyManagerFactory.getInstance("SunX509")
        keyMngFactory.init(keyStore, keystorePassword.toCharArray())
        sslContext.init(keyMngFactory.keyManagers, null, null)
        it.socketFactory = sslContext.socketFactory