Merge "Batch fetching of key descriptors from Keystore"
This commit is contained in:
commit
2051907ae1
@ -160,6 +160,15 @@ public class KeyStore2 {
|
||||
return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
* List all entries in the keystore for in the given namespace.
|
||||
*/
|
||||
public KeyDescriptor[] listBatch(int domain, long namespace, String startPastAlias)
|
||||
throws KeyStoreException {
|
||||
return handleRemoteExceptionWithRetry(
|
||||
(service) -> service.listEntriesBatched(domain, namespace, startPastAlias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant string prefix as used by the keystore boringssl engine. Must be kept in sync
|
||||
* with system/security/keystore-engine. Note: The prefix here includes the 0x which
|
||||
@ -301,6 +310,13 @@ public class KeyStore2 {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of Keystore entries for a given domain and namespace.
|
||||
*/
|
||||
public int getNumberOfEntries(int domain, long namespace) throws KeyStoreException {
|
||||
return handleRemoteExceptionWithRetry((service)
|
||||
-> service.getNumberOfEntries(domain, namespace));
|
||||
}
|
||||
protected static void interruptedPreservingSleep(long millis) {
|
||||
boolean wasInterrupted = false;
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
|
@ -79,13 +79,11 @@ import java.security.spec.NamedParameterSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@ -1043,26 +1041,22 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getUniqueAliases() {
|
||||
private KeyDescriptor[] getAliasesBatch(String startPastAlias) {
|
||||
try {
|
||||
final KeyDescriptor[] keys = mKeyStore.list(
|
||||
return mKeyStore.listBatch(
|
||||
getTargetDomain(),
|
||||
mNamespace
|
||||
mNamespace,
|
||||
startPastAlias
|
||||
);
|
||||
final Set<String> aliases = new HashSet<>(keys.length);
|
||||
for (KeyDescriptor d : keys) {
|
||||
aliases.add(d.alias);
|
||||
}
|
||||
return aliases;
|
||||
} catch (android.security.KeyStoreException e) {
|
||||
Log.e(TAG, "Failed to list keystore entries.", e);
|
||||
return new HashSet<>();
|
||||
return new KeyDescriptor[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> engineAliases() {
|
||||
return Collections.enumeration(getUniqueAliases());
|
||||
return new KeyEntriesEnumerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1073,12 +1067,18 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
|
||||
return getKeyMetadata(alias) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSize() {
|
||||
return getUniqueAliases().size();
|
||||
try {
|
||||
return mKeyStore.getNumberOfEntries(
|
||||
getTargetDomain(),
|
||||
mNamespace
|
||||
);
|
||||
} catch (android.security.KeyStoreException e) {
|
||||
Log.e(TAG, "Failed to get the number of keystore entries.", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsKeyEntry(String alias) {
|
||||
return isKeyEntry(alias);
|
||||
@ -1251,4 +1251,38 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
+ "or TrustedCertificateEntry; was " + entry);
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyEntriesEnumerator implements Enumeration<String> {
|
||||
private KeyDescriptor[] mCurrentBatch;
|
||||
private int mCurrentEntry = 0;
|
||||
private String mLastAlias = null;
|
||||
private KeyEntriesEnumerator() {
|
||||
getAndValidateNextBatch();
|
||||
}
|
||||
|
||||
private void getAndValidateNextBatch() {
|
||||
mCurrentBatch = getAliasesBatch(mLastAlias);
|
||||
mCurrentEntry = 0;
|
||||
}
|
||||
|
||||
public boolean hasMoreElements() {
|
||||
return (mCurrentBatch != null) && (mCurrentBatch.length > 0);
|
||||
}
|
||||
|
||||
public String nextElement() {
|
||||
if ((mCurrentBatch == null) || (mCurrentBatch.length == 0)) {
|
||||
throw new NoSuchElementException("Error while fetching entries.");
|
||||
}
|
||||
final KeyDescriptor currentEntry = mCurrentBatch[mCurrentEntry];
|
||||
mLastAlias = currentEntry.alias;
|
||||
|
||||
mCurrentEntry++;
|
||||
// This was the last entry in the batch.
|
||||
if (mCurrentEntry >= mCurrentBatch.length) {
|
||||
getAndValidateNextBatch();
|
||||
}
|
||||
|
||||
return mLastAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,14 @@
|
||||
package android.security.keystore2;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.isNull;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.security.KeyStore2;
|
||||
@ -36,6 +41,12 @@ import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class AndroidKeyStoreSpiTest {
|
||||
|
||||
@Mock
|
||||
@ -48,14 +59,176 @@ public class AndroidKeyStoreSpiTest {
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesReturnsEmptySetOnKeyStoreError() throws Exception {
|
||||
when(mKeystore2.list(anyInt(), anyLong()))
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), isNull()))
|
||||
.thenThrow(new KeyStoreException(6, "Some Error"));
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
assertThat("Empty collection expected", !spi.engineAliases().hasMoreElements());
|
||||
|
||||
verify(mKeystore2).list(anyInt(), anyLong());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsZeroEntriesEmptyArray() throws Exception {
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), anyString()))
|
||||
.thenReturn(new KeyDescriptor[0]);
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
Enumeration<String> aliases = spi.engineAliases();
|
||||
assertThat("Should not have any elements", !aliases.hasMoreElements());
|
||||
assertThrows("Should have no elements to return", NoSuchElementException.class,
|
||||
() -> aliases.nextElement());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsZeroEntriesNullArray() throws Exception {
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), anyString()))
|
||||
.thenReturn(null);
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
Enumeration<String> aliases = spi.engineAliases();
|
||||
assertThat("Should not have any elements", !aliases.hasMoreElements());
|
||||
assertThrows("Should have no elements to return", NoSuchElementException.class,
|
||||
() -> aliases.nextElement());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
|
||||
}
|
||||
|
||||
private static KeyDescriptor newKeyDescriptor(String alias) {
|
||||
KeyDescriptor result = new KeyDescriptor();
|
||||
result.alias = alias;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static KeyDescriptor[] createKeyDescriptorsArray(int numEntries) {
|
||||
KeyDescriptor[] kds = new KeyDescriptor[numEntries];
|
||||
for (int i = 0; i < kds.length; i++) {
|
||||
kds[i] = newKeyDescriptor(String.format("alias-%d", i));
|
||||
}
|
||||
|
||||
return kds;
|
||||
}
|
||||
|
||||
private static void assertAliasListsEqual(
|
||||
KeyDescriptor[] keyDescriptors, Enumeration<String> aliasesEnumerator) {
|
||||
List<String> aliases = Collections.list(aliasesEnumerator);
|
||||
Assert.assertArrayEquals(Arrays.stream(keyDescriptors).map(kd -> kd.alias).toArray(),
|
||||
aliases.toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsEntriesInASingleBatch() throws Exception {
|
||||
final String alias1 = "testAlias1";
|
||||
final String alias2 = "testAlias2";
|
||||
final String alias3 = "testAlias3";
|
||||
KeyDescriptor[] kds = {newKeyDescriptor(alias1),
|
||||
newKeyDescriptor(alias2), newKeyDescriptor(alias3)};
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
|
||||
.thenReturn(kds);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("testAlias3")))
|
||||
.thenReturn(null);
|
||||
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
Enumeration<String> aliases = spi.engineAliases();
|
||||
assertThat("Should have more elements before first.", aliases.hasMoreElements());
|
||||
Assert.assertEquals(aliases.nextElement(), alias1);
|
||||
assertThat("Should have more elements before second.", aliases.hasMoreElements());
|
||||
Assert.assertEquals(aliases.nextElement(), alias2);
|
||||
assertThat("Should have more elements before third.", aliases.hasMoreElements());
|
||||
Assert.assertEquals(aliases.nextElement(), alias3);
|
||||
assertThat("Should have no more elements after third.", !aliases.hasMoreElements());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("testAlias3"));
|
||||
verifyNoMoreInteractions(mKeystore2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsEntriesInMultipleBatches() throws Exception {
|
||||
final int numEntries = 2500;
|
||||
KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
|
||||
.thenReturn(Arrays.copyOfRange(kds, 0, 1000));
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
|
||||
.thenReturn(Arrays.copyOfRange(kds, 1000, 2000));
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-1999")))
|
||||
.thenReturn(Arrays.copyOfRange(kds, 2000, 2500));
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-2499")))
|
||||
.thenReturn(null);
|
||||
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
assertAliasListsEqual(kds, spi.engineAliases());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-1999"));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-2499"));
|
||||
verifyNoMoreInteractions(mKeystore2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsExactlyOneBatchSize()
|
||||
throws Exception {
|
||||
final int numEntries = 1000;
|
||||
KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
|
||||
.thenReturn(kds);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
|
||||
.thenReturn(null);
|
||||
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
assertAliasListsEqual(kds, spi.engineAliases());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
|
||||
verifyNoMoreInteractions(mKeystore2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsAMultiplyOfBatchSize()
|
||||
throws Exception {
|
||||
final int numEntries = 2000;
|
||||
KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
|
||||
.thenReturn(Arrays.copyOfRange(kds, 0, 1000));
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
|
||||
.thenReturn(Arrays.copyOfRange(kds, 1000, 2000));
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-1999")))
|
||||
.thenReturn(null);
|
||||
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
assertAliasListsEqual(kds, spi.engineAliases());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-1999"));
|
||||
verifyNoMoreInteractions(mKeystore2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineAliasesCorrectlyListsEntriesWhenReturningLessThanBatchSize()
|
||||
throws Exception {
|
||||
final int numEntries = 500;
|
||||
KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
|
||||
.thenReturn(kds);
|
||||
when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-499")))
|
||||
.thenReturn(new KeyDescriptor[0]);
|
||||
|
||||
AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
|
||||
spi.initForTesting(mKeystore2);
|
||||
|
||||
assertAliasListsEqual(kds, spi.engineAliases());
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
|
||||
verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-499"));
|
||||
verifyNoMoreInteractions(mKeystore2);
|
||||
}
|
||||
|
||||
@Mock
|
||||
|
Loading…
x
Reference in New Issue
Block a user