From 9de28222381ba06103f2dfcd3cafe9ebc84bc982 Mon Sep 17 00:00:00 2001 From: C Shri Akhil Date: Sun, 28 Oct 2018 17:03:05 +0530 Subject: [PATCH 1/3] Add Android P support for SecureCredentialsManager Fixes #187 --- .../authentication/storage/CryptoUtil.java | 20 +++++++- .../storage/CryptoUtilTest.java | 48 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java b/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java index baa8c7249..8181da312 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java @@ -85,7 +85,7 @@ KeyStore.PrivateKeyEntry getRSAKeyEntry() throws KeyException { keyStore.load(null); if (keyStore.containsAlias(KEY_ALIAS)) { //Return existing key - return (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); + return getKeyEntryCompat(keyStore); } Calendar start = Calendar.getInstance(); @@ -133,7 +133,8 @@ KeyStore.PrivateKeyEntry getRSAKeyEntry() throws KeyException { KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM_RSA, ANDROID_KEY_STORE); generator.initialize(spec); generator.generateKeyPair(); - return (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); + + return getKeyEntryCompat(keyStore); } catch (KeyStoreException | IOException | NoSuchProviderException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertificateException e) { Log.e(TAG, "An error occurred while trying to obtain the RSA KeyPair Entry from the Android KeyStore.", e); throw new KeyException("An error occurred while trying to obtain the RSA KeyPair Entry from the Android KeyStore.", e); @@ -145,6 +146,21 @@ KeyStore.PrivateKeyEntry getRSAKeyEntry() throws KeyException { } } + private KeyStore.PrivateKeyEntry getKeyEntryCompat(KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + return (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); + } + + //Following code is for API 28+ + PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, null); + + if (privateKey == null) { + return (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); + } + + Certificate certificate = keyStore.getCertificate(KEY_ALIAS); + return new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{certificate}); + } //Used to delete recreate the key pair in case of error private void deleteKeys() { diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java index b24245dc6..32e0d8bfc 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java @@ -300,6 +300,54 @@ public void shouldCreateRSAKeyPairIfMissingOnAPI23AndUp() throws Exception { ArgumentCaptor endDateCaptor = ArgumentCaptor.forClass(Date.class); + final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry(); + + + Mockito.verify(builder).setKeySize(2048); + Mockito.verify(builder).setCertificateSubject(principalCaptor.capture()); + Mockito.verify(builder).setCertificateSerialNumber(BigInteger.ONE); + Mockito.verify(builder).setCertificateNotBefore(startDateCaptor.capture()); + Mockito.verify(builder).setCertificateNotAfter(endDateCaptor.capture()); + Mockito.verify(builder).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + Mockito.verify(builder).setBlockModes(KeyProperties.BLOCK_MODE_ECB); + Mockito.verify(keyPairGenerator).initialize(spec); + Mockito.verify(keyPairGenerator).generateKeyPair(); + + assertThat(principalCaptor.getValue(), is(notNullValue())); + assertThat(principalCaptor.getValue().getName(), is(CERTIFICATE_PRINCIPAL)); + + assertThat(startDateCaptor.getValue(), is(notNullValue())); + long diffMillis = startDateCaptor.getValue().getTime() - new Date().getTime(); + long days = TimeUnit.MILLISECONDS.toDays(diffMillis); + assertThat(days, is(0L)); //Date is Today + + assertThat(endDateCaptor.getValue(), is(notNullValue())); + diffMillis = endDateCaptor.getValue().getTime() - new Date().getTime(); + days = TimeUnit.MILLISECONDS.toDays(diffMillis); + assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days + + assertThat(entry, is(expectedEntry)); + } + + @RequiresApi(api = Build.VERSION_CODES.P) + @Test + @Config(constants = com.auth0.android.auth0.BuildConfig.class, sdk = 28, manifest = Config.NONE) + public void shouldCreateRSAKeyPairIfMissingOnAPI28AndUp() throws Exception { + ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 28); + + PowerMockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); + KeyStore.PrivateKeyEntry expectedEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); + PowerMockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry); + + KeyGenParameterSpec spec = PowerMockito.mock(KeyGenParameterSpec.class); + KeyGenParameterSpec.Builder builder = newKeyGenParameterSpecBuilder(spec); + PowerMockito.whenNew(KeyGenParameterSpec.Builder.class).withArguments(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT).thenReturn(builder); + + ArgumentCaptor principalCaptor = ArgumentCaptor.forClass(X500Principal.class); + ArgumentCaptor startDateCaptor = ArgumentCaptor.forClass(Date.class); + ArgumentCaptor endDateCaptor = ArgumentCaptor.forClass(Date.class); + + final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry(); Mockito.verify(builder).setKeySize(2048); From a9016214e006fd7e16a3c73ec275f8bdde45ea31 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 26 Dec 2018 16:06:32 -0300 Subject: [PATCH 2/3] fix old test configuration --- .../auth0/android/authentication/storage/CryptoUtilTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java index 32e0d8bfc..4220dedcf 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java @@ -331,7 +331,7 @@ public void shouldCreateRSAKeyPairIfMissingOnAPI23AndUp() throws Exception { @RequiresApi(api = Build.VERSION_CODES.P) @Test - @Config(constants = com.auth0.android.auth0.BuildConfig.class, sdk = 28, manifest = Config.NONE) + @Config(constants = com.auth0.android.auth0.BuildConfig.class, sdk = 28) public void shouldCreateRSAKeyPairIfMissingOnAPI28AndUp() throws Exception { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 28); @@ -927,4 +927,4 @@ public Cipher answer(InvocationOnMock invocation) throws Throwable { }); return cryptoUtil; } -} \ No newline at end of file +} From 567483a436dd69c4e5b8996dbc5a62bf92d9edbc Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 26 Dec 2018 16:25:06 -0300 Subject: [PATCH 3/3] fix old test configuration --- .../auth0/android/authentication/storage/CryptoUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java index 4220dedcf..ddbfcc386 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java @@ -331,7 +331,7 @@ public void shouldCreateRSAKeyPairIfMissingOnAPI23AndUp() throws Exception { @RequiresApi(api = Build.VERSION_CODES.P) @Test - @Config(constants = com.auth0.android.auth0.BuildConfig.class, sdk = 28) + @Config(sdk = 28) public void shouldCreateRSAKeyPairIfMissingOnAPI28AndUp() throws Exception { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 28);