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

Commit 5bb8c65e authored by Eran Messeri's avatar Eran Messeri Committed by Automerger Merge Worker
Browse files

Merge "Batch fetching of key descriptors from Keystore" am: 2051907a

parents 932f3e61 2051907a
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