Skip to content

fix: keep RSA key for PKCS1 migration on padding mismatch - #1107

Merged
utkrishtsahu merged 1 commit into
v3.21.2from
fix/1696-keep-rsa-key-for-pkcs1-migration
Oct 26, 2026
Merged

utkrishtsahu merged 1 commit into
v3.21.2from
fix/1696-keep-rsa-key-for-pkcs1-migration

Conversation

@utkrishtsahu

@utkrishtsahu utkrishtsahu commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Users who saved credentials with Auth0.Android 3.12.1 or older (incl. 2.x) are logged out on the first launch after updating to 3.21.1 / 3.21.2 (and 4.2.0).

Root cause. Those versions created a PKCS1-only RSA key. Since #1048, when RSADecrypt gets InvalidKeyException (Keystore INCOMPATIBLE_PADDING_MODE), it deletes the RSA and AES keys before throwing. Its only caller, tryRecoverCurrentAESKey, then runs attemptPKCS1Migration, which needs that RSA key. The key is already gone (No RSA key found for migration), so new keys are generated and the stored credentials are cleared.

The ProviderException branch has a smaller version of the same problem: deleteAESKeys() also removes the legacy IV. Credentials saved by SDK 2.x use the legacy format, so on devices that report the padding mismatch as ProviderException, the AES key migrates but the credentials can't be decrypted afterwards.

Fix (CryptoUtil.RSADecrypt only):

  • InvalidKeyException | InvalidAlgorithmParameterException: no longer deletes keys; still throws CryptoException (never IncompatibleDeviceException).
  • ProviderException: no longer calls deleteAESKeys(); still throws CryptoException.
  • Comments and log messages updated to cover both causes (legacy PKCS1 key, MGF1 digest mismatch).

Recovery stays in tryRecoverCurrentAESKey: it tries the PKCS1 migration first, and if that fails it deletes both keys and a new key pair is generated.

Case Before After
Legacy PKCS1 key (2.x / ≤ 3.12.x) Keys deleted → logged out Migrated to OAEP, stays logged in
PKCS1 key + ProviderException, 2.x data Legacy IV deleted → logged out IV kept, stays logged in
OAEP key with MGF1 digest mismatch (#1016 / #1048) Keys regenerated Unchanged: PKCS1 attempt fails, keys regenerated (deleted one step later)
OAEP key (3.13+), fresh install Works Unchanged

No public API changes.

Testing

Unit tests (CryptoUtilTest)

  • Updated shouldKeepKeysAndThrowCryptoExceptionOn{InvalidKeyException,InvalidAlgorithmParameterException}WhenTryingToRSADecrypt and shouldWrapProviderExceptionFromCipherInitInRSADecryptAsCryptoException to assert that RSADecrypt no longer deletes keys.
  • Added tests that run the real RSADecrypt before the migration, with a KeyStore mock where a deleted key is really gone. The existing migration tests stub RSADecrypt, which is why this wasn't caught:
  • Without the fix, 6 of these tests fail; the MGF1 test passes with and without it.
  • ./gradlew testReleaseUnitTest lintRelease: all unit tests pass, lint 0 errors.

Device (Pixel 10, Android 17). Each run is an in-place update (adb install -r) of a sample app that uses SecureCredentialsManager, with a real login against a test tenant:

# Scenario Steps Key logs Result
1 2.10.2 → this fix Log in on 2.10.2 (key [PKCS1Padding], legacy key_iv), update, launch RSA key rejected … (legacy PKCS1 key or MGF1 digest mismatch) → PKCS1 migration successful → AES key re-encrypted with OAEP and stored ✅ Stays logged in; key now [OAEPPadding], key_iv kept; cold start: no migration, success
2 3.12.0 → this fix Log in on 3.12.0 (key [PKCS1Padding]), update, launch Same as #1 ✅ Stays logged in; cold start: no migration, success
3 MGF1 mismatch (#1016) on 3.21.1 (baseline) Recreate the RSA key with OAEP and MGF1 digest SHA-256 only, log in, getCredentials(), then log in again INCOMPATIBLE_MGF_DIGEST → Deleting corrupted keys → New AES key generated ✅ CRYPTO_EXCEPTION, keys regenerated; second login + getCredentials() succeed
4 MGF1 mismatch on this fix Same as #3 RSA key rejected … → Migration failed due to key access error → Deleting corrupted keys → New AES key generated ✅ Same as #3: CRYPTO_EXCEPTION (not INCOMPATIBLE_DEVICE); second login + getCredentials() succeed
5 3.21.0 (OAEP key) → this fix Log in on 3.21.0, update, launch, cold start No CryptoUtil logs ✅ Stays logged in, no migration, key unchanged
6 Credentials already cleared by 3.21.1 → this fix 3.12.0 log in → 3.21.1 → this fix → log in again No CryptoUtil warnings ✅ No Credentials were previously set; new login, getCredentials() and cold start succeed
7 2.10.2 → 4.2.0 + this fix (same CryptoUtil patch applied to main) Log in on 2.10.2 (key [PKCS1Padding], legacy key_iv), update, launch Same as #1 ✅ Stays logged in; key now [OAEPPadding], key_iv kept; cold start: no migration, success
  • This change adds unit test coverage
  • This change adds integration test coverage
  • This change has been tested on the latest version of the platform/language or why not

Checklist

RSADecrypt deleted the RSA/AES keys on InvalidKeyException (and the AES key + legacy IV on ProviderException) before tryRecoverCurrentAESKey could run the PKCS1 migration, logging out users upgrading from SDK <= 3.12.x / 2.x. RSADecrypt now only throws CryptoException; recovery and cleanup stay in tryRecoverCurrentAESKey, so the MGF1 regeneration from #1048 is unchanged.

Fixes auth0/react-native-auth0#1696

Co-Authored-By: Claude
@utkrishtsahu
utkrishtsahu requested a review from a team as a code owner October 26, 2026 04:41
@coderabbitai

coderabbitai Bot commented Oct 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3a085917-242c-4e00-ad70-50f13cc07004

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants