Robolectric AndroidKeyStore not found, KeyStore.getInstance

780 Views Asked by At

I have a few unit tests where the underlying code makes use of EncryptedSharedPreferences. Because of said code, I'm receiving Exceptions where Robolectric is unable both handle and shadow the native KeyStore implementation. There's currently an open issue on Robolectric's Github, which can be found here. But it does not seem to have any fix on Android >= 11. Any ideas or perhaps you've encountered the same issue?

KeyStoreException is being thrown

AndroidKeyStore not found
java.security.KeyStoreException: AndroidKeyStore not found
    at java.base/java.security.KeyStore.getInstance(KeyStore.java:878)
    at androidx.security.crypto.MasterKeys.keyExists(MasterKeys.java:135)
    at androidx.security.crypto.MasterKeys.getOrCreate(MasterKeys.java:87)
    at ...auth.EncryptedAuthState.sharedPreferences(EncryptedAuthState.kt:88)
    at ...auth.EncryptedAuthState.readState(EncryptedAuthState.kt:57)
    at ...auth.EncryptedAuthState.getCurrent(EncryptedAuthState.kt:40)
    at ...auth.EncryptedAuthState.updateAfterTokenResponse(EncryptedAuthState.kt:74)
    at ...EncryptedAuthStateTest.updateIdTokenOnTokenResponse(EncryptedAuthStateTest.kt:126)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:591)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:274)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:88)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.security.NoSuchAlgorithmException: AndroidKeyStore KeyStore not available
    at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:159)
    at java.base/java.security.Security.getImpl(Security.java:700)
    at java.base/java.security.KeyStore.getInstance(KeyStore.java:875)

When running a test such as

@ExperimentalFoundationApi
@ExperimentalStdlibApi
@RunWith(RobolectricTestRunner::class)
@Config(shadows = [KeyStoreShadow::class])
class EncryptedAuthStateTest {
    @Test
    fun initialAuthState() {
        val state =
            EncryptedAuthState(InstrumentationRegistry.getInstrumentation().targetContext)
                .getCurrent()

        Assert.assertFalse(state.isAuthorized)
        Assert.assertNull(state.accessToken)
        Assert.assertNull(state.accessTokenExpirationTime)
        Assert.assertNull(state.idToken)
        Assert.assertNull(state.refreshToken)
        Assert.assertNull(state.lastAuthorizationResponse)
        Assert.assertNull(state.lastTokenResponse)
        Assert.assertNull(state.lastRegistrationResponse)
        Assert.assertNull(state.scope)
        Assert.assertNull(state.scopeSet)
    }
}

EncryptedAuthState

private fun sharedPreferences(): SharedPreferences {
        val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build()

        return EncryptedSharedPreferences.create(
            context,
            "AuthState",
            masterKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )
    }

I've tried to create a Shadow class, but this is not being triggered:

import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

@Implements(KeyStore.class)
public class KeyStoreShadow {
    @Implementation
    public static KeyStore getInstance(String type)
            throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(new FileInputStream(
                System.getProperty("java.home") + "/lib/security/cacerts"), null);
        return keyStore;
    }
}

Resources

0

There are 0 best solutions below