Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2051907a authored by Eran Messeri's avatar Eran Messeri Committed by Gerrit Code Review
Browse files

Merge "Batch fetching of key descriptors from Keystore"

parents c056bd22 17ad506f
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -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();
+50 −16
Original line number Diff line number Diff line
@@ -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;
        }
    }
}
+175 −2
Original line number Diff line number Diff line
@@ -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