1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.security.keystore2;
18 
19 import static org.hamcrest.MatcherAssert.assertThat;
20 import static org.junit.Assert.assertThrows;
21 import static org.mockito.Mockito.anyInt;
22 import static org.mockito.Mockito.anyLong;
23 import static org.mockito.Mockito.anyString;
24 import static org.mockito.Mockito.eq;
25 import static org.mockito.Mockito.isNull;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.verifyNoMoreInteractions;
28 import static org.mockito.Mockito.when;
29 
30 import android.security.KeyStore2;
31 import android.security.KeyStoreException;
32 import android.security.KeyStoreSecurityLevel;
33 import android.system.keystore2.Authorization;
34 import android.system.keystore2.Domain;
35 import android.system.keystore2.KeyDescriptor;
36 import android.system.keystore2.KeyMetadata;
37 
38 import org.junit.Assert;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.mockito.Mock;
42 import org.mockito.MockitoAnnotations;
43 
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.Enumeration;
47 import java.util.List;
48 import java.util.NoSuchElementException;
49 
50 public class AndroidKeyStoreSpiTest {
51 
52     @Mock
53     private KeyStore2 mKeystore2;
54 
55     @Before
setUp()56     public void setUp() {
57         MockitoAnnotations.initMocks(this);
58     }
59 
60     @Test
testEngineAliasesReturnsEmptySetOnKeyStoreError()61     public void testEngineAliasesReturnsEmptySetOnKeyStoreError() throws Exception {
62         when(mKeystore2.listBatch(anyInt(), anyLong(), isNull()))
63                 .thenThrow(new KeyStoreException(6, "Some Error"));
64         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
65         spi.initForTesting(mKeystore2);
66 
67         assertThat("Empty collection expected", !spi.engineAliases().hasMoreElements());
68 
69         verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
70     }
71 
72     @Test
testEngineAliasesCorrectlyListsZeroEntriesEmptyArray()73     public void testEngineAliasesCorrectlyListsZeroEntriesEmptyArray() throws Exception {
74         when(mKeystore2.listBatch(anyInt(), anyLong(), anyString()))
75                 .thenReturn(new KeyDescriptor[0]);
76         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
77         spi.initForTesting(mKeystore2);
78 
79         Enumeration<String> aliases = spi.engineAliases();
80         assertThat("Should not have any elements", !aliases.hasMoreElements());
81         assertThrows("Should have no elements to return", NoSuchElementException.class,
82                 () -> aliases.nextElement());
83         verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
84     }
85 
86     @Test
testEngineAliasesCorrectlyListsZeroEntriesNullArray()87     public void testEngineAliasesCorrectlyListsZeroEntriesNullArray() throws Exception {
88         when(mKeystore2.listBatch(anyInt(), anyLong(), anyString()))
89                 .thenReturn(null);
90         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
91         spi.initForTesting(mKeystore2);
92 
93         Enumeration<String> aliases = spi.engineAliases();
94         assertThat("Should not have any elements", !aliases.hasMoreElements());
95         assertThrows("Should have no elements to return", NoSuchElementException.class,
96                 () -> aliases.nextElement());
97         verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
98     }
99 
newKeyDescriptor(String alias)100     private static KeyDescriptor newKeyDescriptor(String alias) {
101         KeyDescriptor result = new KeyDescriptor();
102         result.alias = alias;
103         return result;
104     }
105 
createKeyDescriptorsArray(int numEntries)106     private static KeyDescriptor[] createKeyDescriptorsArray(int numEntries) {
107         KeyDescriptor[] kds = new KeyDescriptor[numEntries];
108         for (int i = 0; i < kds.length; i++) {
109             kds[i] = newKeyDescriptor(String.format("alias-%d", i));
110         }
111 
112         return kds;
113     }
114 
assertAliasListsEqual( KeyDescriptor[] keyDescriptors, Enumeration<String> aliasesEnumerator)115     private static void assertAliasListsEqual(
116             KeyDescriptor[] keyDescriptors, Enumeration<String> aliasesEnumerator) {
117         List<String> aliases = Collections.list(aliasesEnumerator);
118         Assert.assertArrayEquals(Arrays.stream(keyDescriptors).map(kd -> kd.alias).toArray(),
119                 aliases.toArray());
120     }
121 
122     @Test
testEngineAliasesCorrectlyListsEntriesInASingleBatch()123     public void testEngineAliasesCorrectlyListsEntriesInASingleBatch() throws Exception {
124         final String alias1 = "testAlias1";
125         final String alias2 = "testAlias2";
126         final String alias3 = "testAlias3";
127         KeyDescriptor[] kds = {newKeyDescriptor(alias1),
128                 newKeyDescriptor(alias2), newKeyDescriptor(alias3)};
129         when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
130                 .thenReturn(kds);
131         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("testAlias3")))
132                 .thenReturn(null);
133 
134         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
135         spi.initForTesting(mKeystore2);
136 
137         Enumeration<String> aliases = spi.engineAliases();
138         assertThat("Should have more elements before first.", aliases.hasMoreElements());
139         Assert.assertEquals(aliases.nextElement(), alias1);
140         assertThat("Should have more elements before second.", aliases.hasMoreElements());
141         Assert.assertEquals(aliases.nextElement(), alias2);
142         assertThat("Should have more elements before third.", aliases.hasMoreElements());
143         Assert.assertEquals(aliases.nextElement(), alias3);
144         assertThat("Should have no more elements after third.", !aliases.hasMoreElements());
145         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
146         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("testAlias3"));
147         verifyNoMoreInteractions(mKeystore2);
148     }
149 
150     @Test
testEngineAliasesCorrectlyListsEntriesInMultipleBatches()151     public void testEngineAliasesCorrectlyListsEntriesInMultipleBatches() throws Exception {
152         final int numEntries = 2500;
153         KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
154         when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
155                 .thenReturn(Arrays.copyOfRange(kds, 0, 1000));
156         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
157                 .thenReturn(Arrays.copyOfRange(kds, 1000, 2000));
158         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-1999")))
159                 .thenReturn(Arrays.copyOfRange(kds, 2000, 2500));
160         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-2499")))
161                 .thenReturn(null);
162 
163         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
164         spi.initForTesting(mKeystore2);
165 
166         assertAliasListsEqual(kds, spi.engineAliases());
167         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
168         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
169         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-1999"));
170         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-2499"));
171         verifyNoMoreInteractions(mKeystore2);
172     }
173 
174     @Test
testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsExactlyOneBatchSize()175     public void testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsExactlyOneBatchSize()
176             throws Exception {
177         final int numEntries = 1000;
178         KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
179         when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
180                 .thenReturn(kds);
181         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
182                 .thenReturn(null);
183 
184         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
185         spi.initForTesting(mKeystore2);
186 
187         assertAliasListsEqual(kds, spi.engineAliases());
188         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
189         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
190         verifyNoMoreInteractions(mKeystore2);
191     }
192 
193     @Test
testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsAMultiplyOfBatchSize()194     public void testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsAMultiplyOfBatchSize()
195             throws Exception {
196         final int numEntries = 2000;
197         KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
198         when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
199                 .thenReturn(Arrays.copyOfRange(kds, 0, 1000));
200         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
201                 .thenReturn(Arrays.copyOfRange(kds, 1000, 2000));
202         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-1999")))
203                 .thenReturn(null);
204 
205         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
206         spi.initForTesting(mKeystore2);
207 
208         assertAliasListsEqual(kds, spi.engineAliases());
209         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
210         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
211         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-1999"));
212         verifyNoMoreInteractions(mKeystore2);
213     }
214 
215     @Test
testEngineAliasesCorrectlyListsEntriesWhenReturningLessThanBatchSize()216     public void testEngineAliasesCorrectlyListsEntriesWhenReturningLessThanBatchSize()
217             throws Exception {
218         final int numEntries = 500;
219         KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
220         when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
221                 .thenReturn(kds);
222         when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-499")))
223                 .thenReturn(new KeyDescriptor[0]);
224 
225         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
226         spi.initForTesting(mKeystore2);
227 
228         assertAliasListsEqual(kds, spi.engineAliases());
229         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
230         verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-499"));
231         verifyNoMoreInteractions(mKeystore2);
232     }
233 
234     @Mock
235     private KeyStoreSecurityLevel mKeystoreSecurityLevel;
236 
descriptor()237     private static KeyDescriptor descriptor() {
238         final KeyDescriptor keyDescriptor = new KeyDescriptor();
239         keyDescriptor.alias = "key";
240         keyDescriptor.blob = null;
241         keyDescriptor.domain = Domain.APP;
242         keyDescriptor.nspace = -1;
243         return keyDescriptor;
244     }
245 
metadata(byte[] cert, byte[] certChain)246     private static KeyMetadata metadata(byte[] cert, byte[] certChain) {
247         KeyMetadata metadata = new KeyMetadata();
248         metadata.authorizations = new Authorization[0];
249         metadata.certificate = cert;
250         metadata.certificateChain = certChain;
251         metadata.key = descriptor();
252         metadata.modificationTimeMs = 0;
253         metadata.keySecurityLevel = 1;
254         return metadata;
255     }
256 
bytes(String string)257     private static byte[] bytes(String string) {
258         return string.getBytes();
259     }
260 
261     class MyPublicKey extends AndroidKeyStorePublicKey {
MyPublicKey(String cert, String chain, KeyStoreSecurityLevel securityLevel)262         MyPublicKey(String cert, String chain, KeyStoreSecurityLevel securityLevel) {
263             super(descriptor(), metadata(cert.getBytes(), chain.getBytes()), "N/A".getBytes(),
264                     "RSA", securityLevel);
265         }
266 
267         @Override
getPrivateKey()268         AndroidKeyStorePrivateKey getPrivateKey() {
269             return null;
270         }
271     }
272 
makePrivateKeyObject(String cert, String chain)273     private AndroidKeyStorePublicKey makePrivateKeyObject(String cert, String chain) {
274         return new MyPublicKey(cert, chain, mKeystoreSecurityLevel);
275     }
276 
277     @Test
testKeystoreKeysAdhereToContractBetweenEqualsAndHashCode()278     public void testKeystoreKeysAdhereToContractBetweenEqualsAndHashCode() throws Exception {
279         AndroidKeyStoreKey key1 = new AndroidKeyStoreKey(descriptor(), 1, new Authorization[0],
280                 "RSA", mKeystoreSecurityLevel);
281         AndroidKeyStoreKey key2 = new AndroidKeyStoreKey(descriptor(), 2, new Authorization[0],
282                 "RSA", mKeystoreSecurityLevel);
283         AndroidKeyStoreKey key1_clone = new AndroidKeyStoreKey(descriptor(), 1,
284                 new Authorization[0], "RSA", mKeystoreSecurityLevel);
285 
286         assertThat("Identity should yield true", key1.equals(key1));
287         Assert.assertEquals("Identity should yield same hash codes",
288                 key1.hashCode(), key1.hashCode());
289         assertThat("Identity should yield true", key2.equals(key2));
290         Assert.assertEquals("Identity should yield same hash codes",
291                 key2.hashCode(), key2.hashCode());
292         assertThat("Different keys should differ", !key1.equals(key2));
293         Assert.assertNotEquals("Different keys should have different hash codes",
294                 key1.hashCode(), key2.hashCode());
295 
296         assertThat("Same keys should yield true", key1.equals(key1_clone));
297         assertThat("Same keys should yield true", key1_clone.equals(key1));
298         Assert.assertEquals("Same keys should yield same hash codes",
299                 key1.hashCode(), key1_clone.hashCode());
300 
301         assertThat("anything.equal(null) should yield false", !key1.equals(null));
302         assertThat("anything.equal(null) should yield false", !key2.equals(null));
303         assertThat("anything.equal(null) should yield false", !key1_clone.equals(null));
304     }
305 
306     @Test
testKeystorePublicKeysAdhereToContractBetweenEqualsAndHashCode()307     public void testKeystorePublicKeysAdhereToContractBetweenEqualsAndHashCode() throws Exception {
308         AndroidKeyStorePublicKey key1 = makePrivateKeyObject("myCert1", "myChain1");
309         AndroidKeyStorePublicKey key2 = makePrivateKeyObject("myCert2", "myChain1");
310         AndroidKeyStorePublicKey key3 = makePrivateKeyObject("myCert1", "myChain3");
311         AndroidKeyStorePublicKey key1_clone = makePrivateKeyObject("myCert1", "myChain1");
312 
313         assertThat("Identity should yield true", key1.equals(key1));
314         Assert.assertEquals("Identity should yield same hash codes",
315                 key1.hashCode(), key1.hashCode());
316         assertThat("Identity should yield true", key2.equals(key2));
317         Assert.assertEquals("Identity should yield same hash codes",
318                 key2.hashCode(), key2.hashCode());
319         assertThat("Identity should yield true", key3.equals(key3));
320         Assert.assertEquals("Identity should yield same hash codes",
321                 key3.hashCode(), key3.hashCode());
322         assertThat("Different keys should differ", !key1.equals(key2));
323         Assert.assertNotEquals("Different keys should have different hash codes",
324                 key1.hashCode(), key2.hashCode());
325         assertThat("Different keys should differ", !key1.equals(key3));
326         Assert.assertNotEquals("Different keys should have different hash codes",
327                 key1.hashCode(), key3.hashCode());
328         assertThat("Different keys should differ", !key2.equals(key3));
329         Assert.assertNotEquals("Different keys should have different hash codes",
330                 key2.hashCode(), key3.hashCode());
331 
332         assertThat("Same keys should yield true", key1.equals(key1_clone));
333         assertThat("Same keys should yield true", key1_clone.equals(key1));
334         Assert.assertEquals("Same keys should yield same hash codes",
335                 key1.hashCode(), key1_clone.hashCode());
336 
337         assertThat("anything.equal(null) should yield false", !key1.equals(null));
338         assertThat("anything.equal(null) should yield false", !key2.equals(null));
339         assertThat("anything.equal(null) should yield false", !key3.equals(null));
340         assertThat("anything.equal(null) should yield false", !key1_clone.equals(null));
341     }
342 }
343