Kevin Etore
Kevin Etore

Reputation: 1144

Robolectric AndroidKeyStore not found, KeyStore.getInstance

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

Upvotes: 4

Views: 775

Answers (0)

Related Questions