androidsslokhttpbouncycastleandroid-keystore

android.security.KeyStoreException: Incompatible purpose when integrating freeRASP library


After adding the library freeRASP Android (version 8.3.0) to our project, when trying to call a secure endpoint, we get SSLException from Retrofit/OkHttp or KeyStore Exception and our api calls fail. We tried updating to 9.0.0 but that didn't fix the problem.

javax.net.ssl.SSLHandshakeException: Read error: ssl=0xb400007aa9f06888: 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:732 0x7aa3ed3791:0x00000000)

Tested with the following device

App configuration:

Happen also with all other test devices we have from Android 10 to 14 (Samsung, Pixel, Huawei, OnePlus, Fairphone)

For the record in our project we have the following libraries Retrofit 2.10.0, Moshi 1.15.1, Koin 3.5.3 , OkHttp 4.12.0, BouncyCastle, we have proguard, SSL pinning in the app via .certificatePinner on the OkHttpClient and we also have a custom sslSocketFactory with TLS protocol.

Moreover, we also get the following issue when trying to log in to the app.

Preferred provider doesn't support key:
    java.security.InvalidKeyException: Keystore operation failed
    at android.security.keystore2.KeyStoreCryptoOperationUtils.getInvalidKeyException(KeyStoreCryptoOperationUtils.java:128)
    at android.security.keystore2.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:152)
    at android.security.keystore2.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:360)
    at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:188)
    at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2985)
    at javax.crypto.Cipher.tryCombinations(Cipher.java:2892)
    at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2797)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:774)
    at javax.crypto.Cipher.init(Cipher.java:1144)
    at javax.crypto.Cipher.init(Cipher.java:1085)
    at com.android.org.conscrypt.CryptoUpcalls.rsaOpWithPrivateKey(CryptoUpcalls.java:180)
    at com.android.org.conscrypt.CryptoUpcalls.rsaSignDigestWithPrivateKey(CryptoUpcalls.java:139)
    at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
    at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:568)
    at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1095)
    at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1079)
    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:896)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.-$$Nest$mprocessDataFromSocket(Unknown Source:0)
    at com.android.org.conscrypt.ConscryptEngineSocket.doHandshake(ConscryptEngineSocket.java:236)
    at com.android.org.conscrypt.ConscryptEngineSocket.startHandshake(ConscryptEngineSocket.java:218)
    at jD.k.g(Unknown Source:105)
    at jD.k.c(Unknown Source:168)
    at jD.e.a(Unknown Source:708)
    at jD.a.a(Unknown Source:57)
    at kD.f.b(Unknown Source:125)
    at hD.a.a(Unknown Source:142)
    at kD.f.b(Unknown Source:125)
    at kD.a.a(Unknown Source:176)
    at kD.f.b(Unknown Source:125)
    at kD.g.a(Unknown Source:144)
    at kD.f.b(Unknown Source:125)
    at jD.i.f(Unknown Source:100)
    at jD.f.run(Unknown Source:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)
Caused by: android.security.KeyStoreException: Incompatible purpose (internal Keystore code: -3 message: system/security/keystore2/src/security_level.rs:310

Caused by:
    0: system/security/keystore2/src/enforcements.rs:563: the purpose is not authorized.
    1: Error::Km(r#INCOMPATIBLE_PURPOSE)) (public error code: 13 internal Keystore code: -3)
    at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:416)
    at android.security.KeyStoreSecurityLevel.createOperation(KeyStoreSecurityLevel.java:122)
    at android.security.keystore2.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:355)
    ... 36 more
Could not find provider for algorithm: RSA/ECB/NoPadding

We have a certificate-based authentication in our app. During log in/enrolment, we create the certificate and store it to the KeyStore. Then, we use the certificate through OkHttp etc...

Removing the RASP library fixes our issue with secure endpoints. Here is the code for initialization:

SecurityDetectionHelper.kt

package com.xxx.lib.security.presentation

import android.content.Context
import com.aheaditec.talsec_security.security.api.Talsec
import com.aheaditec.talsec_security.security.api.TalsecConfig
import com.aheaditec.talsec_security.security.api.ThreatListener
import com.xxx.lib.BuildConfig.ANDROID_SUPPORT_MAIL
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.DEBUGGER_DETECTED
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.EMULATOR_DETECTED
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.HOOK_DETECTED
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.OBFUSCATION_ISSUES_DETECTED
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.ROOT_DETECTED
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.TAMPER_DETECTED
import com.xxx.lib.security.presentation.SecurityThreatDetectedError.Reason.UNTRUSTED_INSTALLATION_SOURCE_DETECTED

class SecurityDetectionHelper(
    private val context: Context,
) : ThreatListener.ThreatDetected, SecurityDetectionHelperInterface {

    private var onThreatDetected: (error: SecurityThreatDetectedError) -> Unit = {}
    private var isEnabled: Boolean = false

    private val supportedAlternativeStores = arrayOf(
        "dev.firebase.appdistribution",
    )

    private val expectedSigningCertificateHashBase64 = arrayOf(
        // App signing
        "xxx",
        // Upload key certificate
        "xxx",
        // Internal test certificate
        "xxx",
    )

    private val config: TalsecConfig
        get() = TalsecConfig(
            PACKAGE_NAME,
            expectedSigningCertificateHashBase64,
            ANDROID_SUPPORT_MAIL,
            supportedAlternativeStores,
            isEnabled,
        )

    override fun onRootDetected() {
        onThreatDetected(SecurityThreatDetectedError(ROOT_DETECTED))
    }

    override fun onDebuggerDetected() {
        onThreatDetected(SecurityThreatDetectedError(DEBUGGER_DETECTED))
    }

    override fun onEmulatorDetected() {
        onThreatDetected(SecurityThreatDetectedError(EMULATOR_DETECTED))
    }

    override fun onTamperDetected() {
        onThreatDetected(SecurityThreatDetectedError(TAMPER_DETECTED))
    }

    override fun onUntrustedInstallationSourceDetected() {
        onThreatDetected(SecurityThreatDetectedError(UNTRUSTED_INSTALLATION_SOURCE_DETECTED))
    }

    override fun onHookDetected() {
        onThreatDetected(SecurityThreatDetectedError(HOOK_DETECTED))
    }

    override fun onDeviceBindingDetected() {
        // do nothing
    }

    override fun onObfuscationIssuesDetected() {
        onThreatDetected(SecurityThreatDetectedError(OBFUSCATION_ISSUES_DETECTED))
    }

    override fun initSecurityDetection(isEnabled: Boolean, onThreatDetected: (error: SecurityThreatDetectedError) -> Unit) {
        this.onThreatDetected = onThreatDetected
        this.isEnabled = isEnabled
        ThreatListener(this).registerListener(context)
        Talsec.start(context, config)
    }

    companion object {
        private const val PACKAGE_NAME = "xxx"
    }
}

MainActivity.kt

class MainActivity : BaseActivity(R.layout.xxx_activity_main) {
    ...
    private val securityDetectionHelper: SecurityDetectionHelperInterface by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        securityDetectionHelper.initSecurityDetection(THREAT_DETECTION_ENABLED, ::showAppBlockedDialog)
        ....
    }
    ...
}

I tried to:

implementation (libs.talsecSecurityCommunity) {
    exclude group: 'com.squareup.okhttp3', module: 'okhttp'
    exclude group: 'org.bouncycastle'
}

Solution

  • freeRASP identified and resolved a conflict between their device binding detection control and TLS/SSL. They discovered an issue with the AndroidKeyStore, which could lead to device binding detection errors. As a temporary solution, they released a special version of their software with device binding disabled.

    Please, use the following dependency:

    implementation 'com.aheaditec.talsec.security:TalsecSecurity-Community:9.6.0-NO_DB'

    see https://github.com/talsec/Free-RASP-Android/issues/31 for details