• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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