okta / okta-mobile-kotlin

Okta's Android Authentication SDK
https://okta.github.io/okta-mobile-kotlin/
Apache License 2.0
32 stars 11 forks source link

Exception decrypting access token #312

Open davidhartley opened 1 week ago

davidhartley commented 1 week ago

Describe the bug?

I'm troubleshooting a bug where a user with a Samsung Galaxy S23 running Android 14 with a freshly installed app, gets an unexpected decrpytion error trying to read the access token from the default credential on app launch. We haven't been able to replicate it on other devices yet.

Using Okta Mobile Kotlin 2.0.1

The method returning the access token: fun getAccessToken(): String? { return Credential.default?.token?.accessToken }

We are migrating over from the old Okta okta-oidc-android in this version of the app. The weird thing is, it works fine for this user when updated from that older version and going through the legacy token migration process. It just throws this exception when installing fresh on this new version without having a previous version. I would just expect this method to return null in this case on fresh install as there is no access token yet. This is currently an unhandled exception as I wasn't expected an error thrown from asking for the credential.

If I handle this exception will the app recover gracefully once a new access token is put in the Credential after authenticating? Any idea what is going on here? It's almost like there's some sort of persistent data between an install that it is trying to decrypt.

The exception is as follows:

javax.crypto.AEADBadTagException caused by Caused by: android.security.KeyStoreException: Signature/MAC verification failed (internal Keystore code: -30 message: system/security/keystore2/src/operation.rs:850: KeystoreOperation::finish

Some additional stack trace information:

javax.crypto.AEADBadTagException
        at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:632)
        at javax.crypto.Cipher.doFinal(Cipher.java:2056)
        at com.okta.authfoundation.util.AesEncryptionHandler.decryptString(AesEncryptionHandler.kt:57)
        at com.okta.authfoundation.credential.DefaultCredentialIdDataStore.getDefaultCredentialId(DefaultCredentialIdDataStore.kt:43)
        at com.okta.authfoundation.credential.DefaultCredentialIdDataStore$getDefaultCredentialId$1.invokeSuspend(Unknown:14)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
        at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
        at kotlinx.coroutines.BuildersKt.runBlocking(Unknown:1)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
        at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown:1)
        at com.okta.authfoundation.credential.Credential$Companion.getDefault(Credential.kt:158)
...
Caused by: android.security.KeyStoreException: Signature/MAC verification failed (internal Keystore code: -30 message: system/security/keystore2/src/operation.rs:850: KeystoreOperation::finish

Caused by:
    0: system/security/keystore2/src/operation.rs:426: Finish failed.
    1: Error::Km(r#VERIFICATION_FAILED))
        at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:435)
        at android.security.KeyStoreOperation.handleExceptions(KeyStoreOperation.java:78)
        at android.security.KeyStoreOperation.finish(KeyStoreOperation.java:128)
        at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer$MainDataStream.finish(KeyStoreCryptoOperationChunkedStreamer.java:228)
        at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:181)
        at android.security.keystore2.AndroidKeyStoreAuthenticatedAESCipherSpi$BufferAllOutputUntilDoFinalStreamer.doFinal(AndroidKeyStoreAuthenticatedAESCipherSpi.java:396)
        at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:624)
        at javax.crypto.Cipher.doFinal(Cipher.java:2056)
        at com.okta.authfoundation.util.AesEncryptionHandler.decryptString(AesEncryptionHandler.kt:57)

What is expected to happen?

Requesting the default credential /access token should not result in an exception and instead should return null if does not exist.

What is the actual behavior?

Fresh installed app on a certain device throws a decrpytion error as if trying to decrypt something already existing.

Reproduction Steps?

Install a fresh app using Okta Mobile Kotlin 2.0.1 and call Credential.default?.token?.accessToken

Unfortunately may be device specific.

Additional Information?

No response

SDK Version and Artifact(s) used.

2.0.1

implementation 'com.okta.kotlin:auth-foundation:2.0.1' implementation 'com.okta.kotlin:web-authentication-ui:2.0.1' implementation 'com.okta.kotlin:legacy-token-migration:2.0.1'

Build Information

No response

rajdeepnanua-okta commented 1 week ago

Hi @davidhartley, thanks for filing the issue! This is a strange bug. I have the same device, so I can check whether I run into this issue or not. For some reason, the datastore for storing default credential ID is providing an invalid non-null value for that user. It might be possible to recover here by catching the exception, and setting a new default credential.

Is it possible that this user has two devices and the app is getting restored from a backup? That would cause this exact issue as well. I am looking into specifying backup filter rules so that encrypted preferences in this SDK don't get restored from a backup.