Skip to content

Commit

Permalink
Add Android P support for SecureCredentialsManager (#203)
Browse files Browse the repository at this point in the history
* Add Android P support for SecureCredentialsManager

Fixes #187

* fix old test configuration

* fix old test configuration
  • Loading branch information
shriakhilc authored and lbalmaceda committed Dec 26, 2018
1 parent ad2a1f8 commit 3c74ca6
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,54 @@ public void shouldCreateRSAKeyPairIfMissingOnAPI23AndUp() throws Exception {
ArgumentCaptor<Date> 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(sdk = 28)
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<X500Principal> principalCaptor = ArgumentCaptor.forClass(X500Principal.class);
ArgumentCaptor<Date> startDateCaptor = ArgumentCaptor.forClass(Date.class);
ArgumentCaptor<Date> endDateCaptor = ArgumentCaptor.forClass(Date.class);


final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry();

Mockito.verify(builder).setKeySize(2048);
Expand Down Expand Up @@ -879,4 +927,4 @@ public Cipher answer(InvocationOnMock invocation) throws Throwable {
});
return cryptoUtil;
}
}
}

0 comments on commit 3c74ca6

Please sign in to comment.