Skip to content

Commit 6a44236

Browse files
committed
Fixing backward compatiblity and UT cases
1 parent 715bcc8 commit 6a44236

File tree

2 files changed

+66
-11
lines changed

2 files changed

+66
-11
lines changed

auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,15 +441,11 @@ byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
441441
}
442442

443443
if (rsaKey != null && keyAliasUsed != null) {
444-
// WARNING: Using PKCS1 padding here is intentional and ONLY for decrypting legacy data.
445-
// This cipher must NEVER be used for encryption or for any new data; always use OAEP instead.
446444
byte[] decryptedAESKey = RSADecryptLegacyPKCS1(
447445
encryptedAESBytes,
448446
rsaKey.getPrivateKey()
449447
);
450-
deleteRSAKeys();
451448

452-
// Re-encrypt AES key with NEW OAEP RSA key (4096-bit)
453449
byte[] encryptedAESWithOAEP = RSAEncrypt(decryptedAESKey);
454450
String newEncodedEncryptedAES = new String(
455451
Base64.encode(encryptedAESWithOAEP, Base64.DEFAULT),

auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.junit.Test;
1717
import org.junit.runner.RunWith;
1818
import org.mockito.ArgumentCaptor;
19+
import org.mockito.BDDMockito;
1920
import org.mockito.Mockito;
2021
import org.mockito.stubbing.Answer;
2122
import org.powermock.api.mockito.PowerMockito;
@@ -37,6 +38,7 @@
3738
import java.security.NoSuchProviderException;
3839
import java.security.PrivateKey;
3940
import java.security.ProviderException;
41+
import java.security.PublicKey;
4042
import java.security.UnrecoverableEntryException;
4143
import java.security.cert.Certificate;
4244
import java.security.cert.CertificateException;
@@ -63,6 +65,7 @@
6365
import static org.mockito.Matchers.anyInt;
6466
import static org.mockito.Matchers.anyString;
6567
import static org.mockito.Matchers.eq;
68+
import static org.mockito.Matchers.isNull;
6669
import static org.mockito.Mockito.never;
6770
import static org.mockito.Mockito.times;
6871
import static org.mockito.Mockito.when;
@@ -82,7 +85,7 @@
8285
* Read more: https://github.com/powermock/powermock/issues/992#issuecomment-662845804
8386
*/
8487
@RunWith(PowerMockRunner.class)
85-
@PrepareForTest({CryptoUtil.class, KeyGenerator.class, TextUtils.class, Build.VERSION.class, Base64.class, Cipher.class, Log.class})
88+
@PrepareForTest({CryptoUtil.class, KeyGenerator.class, TextUtils.class, Build.VERSION.class, Base64.class, Cipher.class, Log.class, KeyStore.class})
8689
public class CryptoUtilTest {
8790

8891
private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
@@ -667,17 +670,19 @@ public void shouldThrowOnInvalidAlgorithmParameterExceptionWhenTryingToObtainRSA
667670
*/
668671

669672
@Test
670-
public void shouldCreateAESKeyIfMissing() {
673+
public void shouldCreateAESKeyIfMissing() throws Exception {
671674
byte[] sampleBytes = new byte[]{0, 1, 2, 3, 4, 5};
672675
PowerMockito.mockStatic(Base64.class);
673676
PowerMockito.when(Base64.encode(sampleBytes, Base64.DEFAULT)).thenReturn("data".getBytes());
674677
PowerMockito.when(storage.retrieveString(KEY_ALIAS)).thenReturn(null);
678+
PowerMockito.when(storage.retrieveString(OLD_KEY_ALIAS)).thenReturn(null);
679+
PowerMockito.mockStatic(TextUtils.class);
680+
PowerMockito.when(TextUtils.isEmpty(null)).thenReturn(true);
675681

676682
SecretKey secretKey = PowerMockito.mock(SecretKey.class);
677683
PowerMockito.when(keyGenerator.generateKey()).thenReturn(secretKey);
678684
PowerMockito.when(secretKey.getEncoded()).thenReturn(sampleBytes);
679-
doReturn(sampleBytes).when(cryptoUtil).RSAEncrypt(sampleBytes);
680-
685+
PowerMockito.doReturn(sampleBytes).when(cryptoUtil, "RSAEncrypt", sampleBytes);
681686

682687
final byte[] aesKey = cryptoUtil.getAESKey();
683688

@@ -740,9 +745,12 @@ public void shouldUseExistingAESKey() {
740745
}
741746

742747
@Test
743-
public void shouldThrowOnNoSuchAlgorithmExceptionWhenCreatingAESKey() {
748+
public void shouldThrowOnNoSuchAlgorithmExceptionWhenCreatingAESKey() throws Exception {
744749
Assert.assertThrows("The device is not compatible with the CryptoUtil class", IncompatibleDeviceException.class, () -> {
745750
PowerMockito.when(storage.retrieveString(KEY_ALIAS)).thenReturn(null);
751+
PowerMockito.when(storage.retrieveString(OLD_KEY_ALIAS)).thenReturn(null);
752+
PowerMockito.mockStatic(TextUtils.class);
753+
PowerMockito.when(TextUtils.isEmpty(null)).thenReturn(true);
746754
PowerMockito.mockStatic(KeyGenerator.class);
747755
PowerMockito.when(KeyGenerator.getInstance(ALGORITHM_AES))
748756
.thenThrow(new NoSuchAlgorithmException());
@@ -1738,7 +1746,7 @@ public void shouldDetectAndMigratePKCS1KeyToOAEP() throws Exception {
17381746

17391747
Mockito.verify(storage).store(KEY_ALIAS, encodedEncryptedAESOAEP);
17401748

1741-
Mockito.verify(keyStore).deleteEntry(KEY_ALIAS);
1749+
// Note: We do NOT verify deleteEntry because RSA keys are preserved for backward compatibility
17421750
}
17431751

17441752
@Test
@@ -1842,7 +1850,58 @@ public void shouldRecognizeIncompatiblePaddingModeInExceptionChain() throws Exce
18421850
byte[] result = cryptoUtil.getAESKey();
18431851
assertThat(result, is(aesKeyBytes));
18441852
Mockito.verify(rsaPkcs1Cipher).doFinal(encryptedAESBytes);
1845-
Mockito.verify(keyStore).deleteEntry(KEY_ALIAS);
1853+
1854+
}
1855+
1856+
@Test
1857+
public void shouldAllowMultipleRetrievalsAfterMigration() throws Exception {
1858+
1859+
CryptoUtil cryptoUtil = newCryptoUtilSpy();
1860+
1861+
byte[] aesKeyBytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
1862+
byte[] encryptedAESKeyPKCS1 = new byte[]{20, 21, 22, 23, 24};
1863+
String encodedEncryptedAESPKCS1 = "pkcs1_encrypted_key";
1864+
1865+
// First retrieval - migration happens
1866+
when(storage.retrieveString(eq(KEY_ALIAS))).thenReturn(encodedEncryptedAESPKCS1);
1867+
when(storage.retrieveString(eq(OLD_KEY_ALIAS))).thenReturn(null);
1868+
PowerMockito.mockStatic(Base64.class);
1869+
PowerMockito.when(Base64.decode(encodedEncryptedAESPKCS1, Base64.DEFAULT)).thenReturn(encryptedAESKeyPKCS1);
1870+
1871+
IncompatibleDeviceException incompatibleException = new IncompatibleDeviceException(
1872+
new KeyStoreException("Incompatible padding mode")
1873+
);
1874+
doThrow(incompatibleException).when(cryptoUtil).RSADecrypt(encryptedAESKeyPKCS1);
1875+
1876+
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true);
1877+
KeyStore.PrivateKeyEntry mockKeyEntry = mock(KeyStore.PrivateKeyEntry.class);
1878+
PrivateKey mockPrivateKey = mock(PrivateKey.class);
1879+
when(mockKeyEntry.getPrivateKey()).thenReturn(mockPrivateKey);
1880+
when(keyStore.getEntry(eq(KEY_ALIAS), nullable(KeyStore.ProtectionParameter.class)))
1881+
.thenReturn(mockKeyEntry);
1882+
1883+
when(rsaPkcs1Cipher.doFinal(encryptedAESKeyPKCS1)).thenReturn(aesKeyBytes);
1884+
1885+
byte[] encryptedAESKeyOAEP = new byte[]{30, 31, 32, 33, 34};
1886+
doReturn(encryptedAESKeyOAEP).when(cryptoUtil).RSAEncrypt(aesKeyBytes);
1887+
String encodedEncryptedAESOAEP = "oaep_encrypted_key";
1888+
PowerMockito.when(Base64.encode(encryptedAESKeyOAEP, Base64.DEFAULT))
1889+
.thenReturn(encodedEncryptedAESOAEP.getBytes(StandardCharsets.UTF_8));
1890+
1891+
byte[] result1 = cryptoUtil.getAESKey();
1892+
assertThat(result1, is(aesKeyBytes));
1893+
Mockito.verify(storage).store(KEY_ALIAS, encodedEncryptedAESOAEP);
1894+
1895+
when(storage.retrieveString(eq(KEY_ALIAS))).thenReturn(encodedEncryptedAESOAEP);
1896+
PowerMockito.when(Base64.decode(encodedEncryptedAESOAEP, Base64.DEFAULT)).thenReturn(encryptedAESKeyOAEP);
1897+
1898+
1899+
doReturn(aesKeyBytes).when(cryptoUtil).RSADecrypt(encryptedAESKeyOAEP);
1900+
1901+
byte[] result2 = cryptoUtil.getAESKey();
1902+
assertThat(result2, is(aesKeyBytes));
1903+
1904+
Mockito.verify(keyStore, never()).deleteEntry(KEY_ALIAS);
18461905
}
18471906

18481907
@Test

0 commit comments

Comments
 (0)