1 /* 2 * Copyright (C) 2022 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 com.android.rkpdapp.unittest; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.fail; 22 23 import android.content.Context; 24 import android.database.sqlite.SQLiteConstraintException; 25 26 import androidx.room.Room; 27 import androidx.test.core.app.ApplicationProvider; 28 import androidx.test.ext.junit.runners.AndroidJUnit4; 29 30 import com.android.rkpdapp.RkpdException; 31 import com.android.rkpdapp.database.InstantConverter; 32 import com.android.rkpdapp.database.ProvisionedKey; 33 import com.android.rkpdapp.database.ProvisionedKeyDao; 34 import com.android.rkpdapp.database.RkpdDatabase; 35 import com.android.rkpdapp.testutil.TestDatabase; 36 import com.android.rkpdapp.testutil.TestProvisionedKeyDao; 37 38 import org.junit.After; 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 import java.time.Duration; 44 import java.time.Instant; 45 import java.time.temporal.ChronoUnit; 46 import java.util.List; 47 48 @RunWith(AndroidJUnit4.class) 49 public class RkpdDatabaseTest { 50 private static final String DB_NAME = "test_db"; 51 private static final String TEST_HAL_1 = "testIrpc"; 52 private static final String TEST_HAL_2 = "someOtherIrpc"; 53 private static final byte[] TEST_KEY_BLOB_1 = new byte[]{0x01, 0x02, 0x03}; 54 private static final byte[] TEST_KEY_BLOB_2 = new byte[]{0x11, 0x12, 0x13}; 55 private static final byte[] TEST_KEY_BLOB_3 = new byte[]{0x21, 0x22, 0x23}; 56 private static final Instant TEST_KEY_EXPIRY = Instant.now().plus(Duration.ofHours(1)); 57 private static final int FAKE_CLIENT_UID = 1; 58 private static final int FAKE_CLIENT_UID_2 = 2; 59 private static final int FAKE_KEY_ID = 1; 60 private static final int FAKE_CLIENT_UID_3 = 3; 61 private static final int FAKE_KEY_ID_2 = 2; 62 private ProvisionedKey mProvisionedKey1; 63 private ProvisionedKey mProvisionedKey2; 64 65 private ProvisionedKeyDao mKeyDao; 66 private RkpdDatabase mDatabase; 67 private TestDatabase mTestDatabase; 68 private TestProvisionedKeyDao mTestDao; 69 70 @Before setUp()71 public void setUp() { 72 Context context = ApplicationProvider.getApplicationContext(); 73 mDatabase = Room.databaseBuilder(context, RkpdDatabase.class, DB_NAME).build(); 74 mKeyDao = mDatabase.provisionedKeyDao(); 75 mKeyDao.deleteAllKeys(); 76 mTestDatabase = Room.databaseBuilder(context, TestDatabase.class, DB_NAME).build(); 77 mTestDao = mTestDatabase.dao(); 78 mProvisionedKey1 = new ProvisionedKey(TEST_KEY_BLOB_1, TEST_HAL_1, TEST_KEY_BLOB_1, 79 TEST_KEY_BLOB_1, TEST_KEY_EXPIRY); 80 mProvisionedKey2 = new ProvisionedKey(TEST_KEY_BLOB_2, TEST_HAL_2, TEST_KEY_BLOB_2, 81 TEST_KEY_BLOB_2, TEST_KEY_EXPIRY); 82 } 83 84 @After tearDown()85 public void tearDown() { 86 mDatabase.close(); 87 mTestDatabase.close(); 88 } 89 90 @Test testWriteToTable()91 public void testWriteToTable() { 92 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 93 List<ProvisionedKey> keysInDatabase = mTestDao.getAllKeys(); 94 95 assertThat(keysInDatabase).containsExactly(mProvisionedKey1); 96 } 97 98 @Test testOverwriteConflict()99 public void testOverwriteConflict() { 100 mProvisionedKey2.keyBlob = TEST_KEY_BLOB_1; 101 try { 102 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 103 fail("Inserting keys with same keyBlob should throw SQLiteConstraintException."); 104 } catch (SQLiteConstraintException ex) { 105 assertThat(ex).hasMessageThat().contains("UNIQUE constraint failed"); 106 } 107 108 List<ProvisionedKey> unassignedKeys = mTestDao.getAllKeys(); 109 assertThat(unassignedKeys).isEmpty(); 110 } 111 112 @Test testRemovingExpiredKeyFromTable()113 public void testRemovingExpiredKeyFromTable() { 114 mProvisionedKey1.expirationTime = Instant.now().minus(1000, ChronoUnit.MINUTES); 115 mProvisionedKey2.expirationTime = Instant.now().plus(1000, ChronoUnit.MINUTES); 116 117 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 118 119 List<ProvisionedKey> keysInDatabase = mTestDao.getAllKeys(); 120 assertThat(keysInDatabase).hasSize(2); 121 122 mKeyDao.deleteExpiringKeys(Instant.now()); 123 124 keysInDatabase = mTestDao.getAllKeys(); 125 assertThat(keysInDatabase).containsExactly(mProvisionedKey2); 126 } 127 128 @Test testAssignedKeysAreAlsoExpired()129 public void testAssignedKeysAreAlsoExpired() { 130 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 131 132 assertThat(mKeyDao.getOrAssignKey(TEST_HAL_1, Instant.now(), FAKE_CLIENT_UID, FAKE_KEY_ID)) 133 .isNotNull(); 134 assertThat(mKeyDao.getKeyForClientAndIrpc(TEST_HAL_1, FAKE_CLIENT_UID, FAKE_KEY_ID)) 135 .isNotNull(); 136 137 mKeyDao.deleteExpiringKeys(mProvisionedKey1.expirationTime.plusMillis(1)); 138 139 assertThat(mKeyDao.getKeyForClientAndIrpc(TEST_HAL_1, FAKE_CLIENT_UID, FAKE_KEY_ID)) 140 .isNull(); 141 } 142 143 @Test testUpdate()144 public void testUpdate() { 145 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 146 147 List<ProvisionedKey> keysInDatabase = mTestDao.getAllKeys(); 148 ProvisionedKey key = keysInDatabase.get(0); 149 assertThat(keysInDatabase).hasSize(1); 150 assertThat(key.expirationTime).isEqualTo( 151 mProvisionedKey1.expirationTime.truncatedTo(ChronoUnit.MILLIS)); 152 153 Instant expiredInstant = InstantConverter.fromTimestamp(System.currentTimeMillis()) 154 .minus(1000, ChronoUnit.MINUTES); 155 key.expirationTime = expiredInstant; 156 mKeyDao.updateKey(key); 157 keysInDatabase = mTestDao.getAllKeys(); 158 assertThat(keysInDatabase).containsExactly(key); 159 assertThat(keysInDatabase.get(0).expirationTime).isEqualTo(expiredInstant); 160 } 161 162 @Test testUpdateWithNonExistentKey()163 public void testUpdateWithNonExistentKey() { 164 mKeyDao.updateKey(mProvisionedKey1); 165 166 assertThat(mTestDao.getAllKeys()).isEmpty(); 167 } 168 169 @Test testDeleteAllKeys()170 public void testDeleteAllKeys() { 171 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 172 173 List<ProvisionedKey> keysInDatabase = mTestDao.getAllKeys(); 174 assertThat(keysInDatabase).hasSize(2); 175 176 mKeyDao.deleteAllKeys(); 177 assertThat(mTestDao.getAllKeys()).isEmpty(); 178 } 179 180 @Test testGetTotalExpiringKeysForIrpc()181 public void testGetTotalExpiringKeysForIrpc() { 182 final Instant past = Instant.now().minus(1000, ChronoUnit.MINUTES); 183 final Instant future = Instant.now().plus(1000, ChronoUnit.MINUTES); 184 185 ProvisionedKey key1 = new ProvisionedKey(TEST_KEY_BLOB_1, TEST_HAL_1, TEST_KEY_BLOB_1, 186 TEST_KEY_BLOB_1, past); 187 ProvisionedKey key2 = new ProvisionedKey(TEST_KEY_BLOB_2, TEST_HAL_1, TEST_KEY_BLOB_2, 188 TEST_KEY_BLOB_2, future); 189 ProvisionedKey key3 = new ProvisionedKey(TEST_KEY_BLOB_3, TEST_HAL_2, TEST_KEY_BLOB_3, 190 TEST_KEY_BLOB_3, past); 191 mKeyDao.insertKeys(List.of(key1, key2, key3)); 192 193 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_1, past)).isEqualTo(0); 194 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_2, past)).isEqualTo(0); 195 196 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_1, past.plusMillis(1))) 197 .isEqualTo(1); 198 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_2, past.plusMillis(1))) 199 .isEqualTo(1); 200 201 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_1, future)).isEqualTo(1); 202 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_2, future)).isEqualTo(1); 203 204 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_1, future.plusMillis(1))) 205 .isEqualTo(2); 206 assertThat(mKeyDao.getTotalExpiringKeysForIrpc(TEST_HAL_2, future.plusMillis(1))) 207 .isEqualTo(1); 208 } 209 210 @Test testGetKeyForClientAndIrpc()211 public void testGetKeyForClientAndIrpc() { 212 mProvisionedKey1.keyId = FAKE_KEY_ID; 213 mProvisionedKey1.clientUid = FAKE_CLIENT_UID; 214 mProvisionedKey2.irpcHal = TEST_HAL_1; 215 mProvisionedKey2.keyId = FAKE_KEY_ID; 216 mProvisionedKey2.clientUid = FAKE_CLIENT_UID_2; 217 218 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 219 220 ProvisionedKey assignedKey = mKeyDao.getKeyForClientAndIrpc(TEST_HAL_1, FAKE_CLIENT_UID, 221 FAKE_KEY_ID); 222 assertThat(mProvisionedKey1).isEqualTo(assignedKey); 223 224 assignedKey = mKeyDao.getKeyForClientAndIrpc(TEST_HAL_1, FAKE_CLIENT_UID_2, FAKE_KEY_ID); 225 assertThat(mProvisionedKey2).isEqualTo(assignedKey); 226 227 assignedKey = mKeyDao.getKeyForClientAndIrpc(TEST_HAL_1, FAKE_CLIENT_UID_3, FAKE_KEY_ID_2); 228 assertThat(assignedKey).isNull(); 229 } 230 231 @Test testUpgradeKeyBlob()232 public void testUpgradeKeyBlob() { 233 mProvisionedKey1.keyId = FAKE_KEY_ID; 234 mProvisionedKey1.clientUid = FAKE_CLIENT_UID; 235 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 236 237 ProvisionedKey databaseKey = mTestDao.getAllKeys().get(0); 238 assertThat(databaseKey.keyBlob).isEqualTo(TEST_KEY_BLOB_1); 239 assertThat(mKeyDao.upgradeKeyBlob(FAKE_CLIENT_UID_2, TEST_KEY_BLOB_1, TEST_KEY_BLOB_2)) 240 .isEqualTo(0); 241 assertThat(mKeyDao.upgradeKeyBlob(FAKE_CLIENT_UID, TEST_KEY_BLOB_1, TEST_KEY_BLOB_2)) 242 .isEqualTo(1); 243 244 databaseKey = mTestDao.getAllKeys().get(0); 245 assertThat(databaseKey.keyBlob).isEqualTo(TEST_KEY_BLOB_2); 246 } 247 248 @Test testCorrectClientUpgradesKeyBlob()249 public void testCorrectClientUpgradesKeyBlob() { 250 mProvisionedKey1.keyId = FAKE_KEY_ID; 251 mProvisionedKey1.clientUid = FAKE_CLIENT_UID; 252 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 253 254 ProvisionedKey databaseKey = mTestDao.getAllKeys().get(0); 255 assertThat(databaseKey.keyBlob).isEqualTo(TEST_KEY_BLOB_1); 256 assertThat(mKeyDao.upgradeKeyBlob(FAKE_CLIENT_UID_2, TEST_KEY_BLOB_1, TEST_KEY_BLOB_2)) 257 .isEqualTo(0); 258 259 databaseKey = mTestDao.getAllKeys().get(0); 260 assertThat(databaseKey.keyBlob).isEqualTo(TEST_KEY_BLOB_1); 261 } 262 263 @Test testUpgradeNonExistentKeyBlob()264 public void testUpgradeNonExistentKeyBlob() { 265 mProvisionedKey1.keyId = FAKE_KEY_ID; 266 mProvisionedKey1.clientUid = FAKE_CLIENT_UID; 267 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 268 assertThat(mKeyDao.upgradeKeyBlob(FAKE_CLIENT_UID, TEST_KEY_BLOB_2, TEST_KEY_BLOB_3)) 269 .isEqualTo(0); 270 } 271 272 @Test testCountUnassignedKeys()273 public void testCountUnassignedKeys() { 274 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 275 assertThat(mKeyDao.getTotalUnassignedKeysForIrpc(TEST_HAL_1)).isEqualTo(1); 276 assertThat(mKeyDao.getTotalUnassignedKeysForIrpc(TEST_HAL_2)).isEqualTo(1); 277 assertThat(mKeyDao.getTotalUnassignedKeysForIrpc("fakeHal")).isEqualTo(0); 278 } 279 280 @Test testAssignKey()281 public void testAssignKey() throws RkpdException { 282 mProvisionedKey2.irpcHal = TEST_HAL_1; 283 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 284 285 List<ProvisionedKey> keysPersisted = mTestDao.getAllKeys(); 286 for (ProvisionedKey databaseKey : keysPersisted) { 287 assertThat(databaseKey.keyId).isNull(); 288 assertThat(databaseKey.clientUid).isNull(); 289 } 290 291 ProvisionedKey assignedKey = mKeyDao.getOrAssignKey(TEST_HAL_1, Instant.now(), 292 FAKE_CLIENT_UID, FAKE_KEY_ID); 293 294 assertThat(assignedKey.keyId).isEqualTo(FAKE_KEY_ID); 295 assertThat(assignedKey.clientUid).isEqualTo(FAKE_CLIENT_UID); 296 297 ProvisionedKey sameKey = mKeyDao.getOrAssignKey(TEST_HAL_1, Instant.now(), FAKE_CLIENT_UID, 298 FAKE_KEY_ID); 299 assertThat(sameKey).isEqualTo(assignedKey); 300 } 301 302 @Test testAssignKeyChoosesNonExpiredKey()303 public void testAssignKeyChoosesNonExpiredKey() throws RkpdException { 304 mProvisionedKey1.expirationTime = Instant.now().minusMillis(1); 305 mProvisionedKey2.irpcHal = TEST_HAL_1; 306 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 307 308 ProvisionedKey assignedKey = mKeyDao.getOrAssignKey(TEST_HAL_1, Instant.now(), 309 FAKE_CLIENT_UID, FAKE_KEY_ID); 310 311 // The first key is expired, so it should not have been assigned 312 assertThat(assignedKey.keyBlob).isNotEqualTo(mProvisionedKey1.publicKey); 313 assertThat(assignedKey.keyBlob).isEqualTo(mProvisionedKey2.publicKey); 314 } 315 316 @Test testAssignKeyFailsIfAllKeysAreExpired()317 public void testAssignKeyFailsIfAllKeysAreExpired() throws RkpdException { 318 mProvisionedKey1.expirationTime = Instant.now().minusMillis(1); 319 mProvisionedKey2.irpcHal = TEST_HAL_1; 320 mProvisionedKey2.expirationTime = Instant.now().minusMillis(1); 321 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 322 323 assertThat(mKeyDao.getOrAssignKey(TEST_HAL_1, Instant.now(), FAKE_CLIENT_UID, 324 FAKE_KEY_ID)).isNull(); 325 } 326 327 @Test testNoUnassignedKeyRemaining()328 public void testNoUnassignedKeyRemaining() { 329 assertThat(mKeyDao.getOrAssignKey(TEST_HAL_1, Instant.now(), FAKE_CLIENT_UID, 330 FAKE_KEY_ID)).isNull(); 331 } 332 333 @Test testUpgradeWithNullKeyBlob()334 public void testUpgradeWithNullKeyBlob() { 335 mProvisionedKey1.keyId = FAKE_KEY_ID; 336 mProvisionedKey1.clientUid = FAKE_CLIENT_UID; 337 mKeyDao.insertKeys(List.of(mProvisionedKey1)); 338 339 try { 340 mKeyDao.upgradeKeyBlob(FAKE_CLIENT_UID, TEST_KEY_BLOB_1, null); 341 fail("UpgradeKeyBlob should fail for null keyblob."); 342 } catch (SQLiteConstraintException ex) { 343 assertThat(ex).hasMessageThat().contains("NOT NULL constraint failed"); 344 } 345 } 346 347 @Test testUpgradeWithDuplicateKeyBlob()348 public void testUpgradeWithDuplicateKeyBlob() { 349 mProvisionedKey1.keyId = FAKE_KEY_ID; 350 mProvisionedKey1.clientUid = FAKE_CLIENT_UID; 351 mProvisionedKey2.keyId = FAKE_KEY_ID_2; 352 mProvisionedKey2.clientUid = FAKE_CLIENT_UID; 353 mKeyDao.insertKeys(List.of(mProvisionedKey1, mProvisionedKey2)); 354 355 try { 356 mKeyDao.upgradeKeyBlob(FAKE_CLIENT_UID, TEST_KEY_BLOB_1, TEST_KEY_BLOB_2); 357 fail("UpgradeKeyBlob should fail for duplicate keyblob."); 358 } catch (SQLiteConstraintException ex) { 359 assertThat(ex).hasMessageThat().contains("UNIQUE constraint failed"); 360 } 361 } 362 } 363