Reputation: 1144
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
JCE cannot authenticate the provider AndroidKeyStore
. Most likely due to Android >= 11 where key stores having to be signedUpvotes: 4
Views: 775