google / conscrypt

Conscrypt is a Java Security Provider that implements parts of the Java Cryptography Extension and Java Secure Socket Extension.
Apache License 2.0
1.28k stars 274 forks source link

W/CryptoUpcalls: Could not find provider for algorithm: RSA/ECB/PKCS1Padding #1030

Open vixrt opened 3 years ago

vixrt commented 3 years ago

I'm developing a kotlin android app, but I get this

W/CryptoUpcalls: Could not find provider for algorithm: RSA/ECB/PKCS1Padding
I/okhttp.OkHttpClient: <-- HTTP FAILED: javax.net.ssl.SSLHandshakeException: Read error: ssl=0x6fde49c498: Failure in SSL library, usually a protocol error
    error:04000044:RSA routines:OPENSSL_internal:internal error (external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc:741 0x6f395ae05a:0x00000000)

I am using android 11

Here is some code that maybe can help better understanding my problem

Create OkHttpClient

  Security.removeProvider("ProviderName")

        val provider = GenerateProvider()
        Security.insertProviderAt(provider , 1)

val builder = OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(15, TimeUnit.SECONDS)

        val keyStore: KeyStore = KeyStore.getInstance("ProviderName")
        keyStore.load(ByteArrayInputStream(certificate), null)

        val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
        kmf.init(keyStore, null)
        val keyManagers = kmf.keyManagers

        val trustManagerFactory =
            TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        trustManagerFactory.init(null as KeyStore?)
        val trustManagers = trustManagerFactory.trustManagers
        if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
            throw IllegalStateException(
                "Unexpected default trust managers:" + Arrays.toString(
                    trustManagers
                )
            )

        }

        val trustManager = trustManagers[0] as X509TrustManager
        val sslContext = SSLContext.getInstance("TLSv1.2")
        sslContext.init(keyManagers, null, null)

        val spec: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
            .supportsTlsExtensions(true)
            .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
            .cipherSuites(
                TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
                TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
                TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
                TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                TLS_AES_128_CCM_8_SHA256,
                TLS_AES_256_GCM_SHA384,
                TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
                TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
                TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
                TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
                TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
                TLS_ECDHE_RSA_WITH_RC4_128_SHA,
                TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
                TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
                TLS_DHE_RSA_WITH_AES_256_CBC_SHA
            )
            .build()

        return builder.sslSocketFactory(sslContext.socketFactory, trustManager)
            .connectionSpecs(Collections.singletonList(spec)).build()

Create Retrofit

Retrofit.Builder().baseUrl(BuildConfig.URL)
            .client(okHttpClient)
            .addConverterFactory(ScalarsConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()

And then I do an http post

postInterface = createRetrofit()
(postInterface?: return).DoPost(mapValues).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(object :
                DisposableSingleObserver<Response<ResponseBody>>() {
                override fun onSuccess(idpResponse: Response<ResponseBody>) {
                    //....
                }

                override fun onError(e: Throwable) {
                    //....
                }
            })

And this is the postInterface


interface PostInterface {

    @Headers("User-Agent: Mozilla/5.0")
    @FormUrlEncoded
    @POST(Endpoints.URL)
    fun DoPost(@FieldMap(encoded = true) values: Map<String, String>): Single<Response<ResponseBody>>

}

Thanks in advance

prbprbprb commented 3 years ago

The final error here is a little unhelpful, sorry, and I think we can improve that in a future release and there's possibly a bug.

The problem here boils down to the custom Provider you are using to build the trust store for the connection, so you should be able to reproduce the same error without okhttp and Retrofit - just open a TLS socket to your web server using the same SslContext.

What's looks to be going on is a little convoluted.

Conscrypt's TLS code is unable to directly use the certificate you load for crypto operations (I'll come back to why), so it wraps it to create a delegated key, which is intended to find a security Provider which can use the key and delegate all operations to that Provider.

The algorithm for finding a suitable provider goes like this:

So, your custom Provider doesn't claim to support RSA/ECB/PKCS1Padding. There are a couple of possible reasons why this might not be a bug:

So, tl;dr Conscrypt doesn't think it can handle the key and it doesn't think any other Provider can handle this key format.

The simple "fix" is to make Conscrypt think your custom Provider can handle the key, which means your provider should advertise support for RSA/ECB/PKCS1Padding, then Conscrypt will find it and delegate crypto operations for this key to it.

Why doesn't Conscrypt think it can handle the key? The key was created by your custom Provider so it's not a subclass of OpenSSLKey which is what Conscrypt uses. If it's a subclass of RsaPrivateKey and key.getFormat() returns PKCS#8 then Conscrypt will generate an OpenSSLKey from the output of key.getEncoded().

However if your custom provider is some kind of hardware keystore then obviously that won't work as the private values won't be accessible, which means the only way to get it to work is as a delegating key, which means Conscrypt needs to be able to find the correct Provider to delegate to, as above.

Also, unless you have compelling reasons to do otherwise, it's better to accept the default TLS config, e.g.

        val sslContext = SSLContext.getInstance("TLS")

and then don't customise the list of supported versions or cipher suites.

Please close this if that answers your question, or assign back to me if I've missed something.

vixrt commented 3 years ago

Hi @prbprbprb , thanks for the reply Now my code is

        val trustManager = trustManagers[0] as X509TrustManager
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(keyManagers, null, null)
        return builder.sslSocketFactory(sslContext.socketFactory, trustManager).build()

At the end of creating the okhttpclient

I tried removing the cipher specifications and to getInstance of "TLS" only but it doesn't work

I have an hardware keystore, it's the official italian identification card.

I simplified the real code, which is from here https://github.com/italia/cieid-android-sdk the official italian repository You can find more here https://github.com/italia/cieid-android-sdk/tree/master/cieidsdk/src/main/java/it/ipzs/cieidsdk/ciekeystore

I still open the issue here and not there for 2 reasons

1) The dev won't release the full code of the app (no one knows why, it goes even against italian laws) 2) I think the issue is within this library, not in the project in general, because that code is used in the official app and the official app works

Thanks again

satur9nine commented 2 years ago

I got hit with this same exact issue to get mutual authentication TLS working with a custom keystore. I had to make some major changes to the client side of security provider to get it to work. I copied a bunch of code from the AndroidKeyStore, specifically search for adjustConfigForEncryptingWithPrivateKey in the keystore provider code: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android10-release/keystore/java/android/security

Also see the code in the provider class that advertises support for a nonsense Cipher with RSA private key (and sensible Cipher with RSA public key):

    putAsymmetricCipherImpl("RSA/ECB/NoPadding",
            PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding");
    put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
    putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding",
            PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding");
    put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding");

private void putAsymmetricCipherImpl(String transformation, String implClass) {
    put("Cipher." + transformation, implClass);
    put("Cipher." + transformation + " SupportedKeyClasses",
            KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
}

The actual implementation of our crypto operations didn't need to change since the client side of the provider switches from encryption to signing before actually performing the operation. It's definitely weird stuff.

Our provider was working fine on Android 8.1, not sure if it was Android 9 or 10 that broke it, we jumped straight from 8.1 to 10.

Bug appears to be on this line:

https://github.com/google/conscrypt/blob/2.5.x/common/src/main/java/org/conscrypt/CryptoUpcalls.java#L137

Suggested fix for conscrypt; why not just perform the intended operation: a Signature with an RSA private key instead of trying to do a Cipher operation? Or try both operations to work with providers that can do only one or the other, then fail if neither Cipher nor Signature with a private RSA key works.