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