blob: 3f5ac8e504b3b1ec455230ffa5058196edc9655e [file] [log] [blame]
Robert Berry76cf0832017-12-15 23:01:22 +00001/*
2 * Copyright (C) 2017 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
17package com.android.server.locksettings.recoverablekeystore.storage;
18
Dmitry Dementyevad884712017-12-20 12:38:36 -080019import android.annotation.NonNull;
Robert Berry76cf0832017-12-15 23:01:22 +000020import android.annotation.Nullable;
21import android.content.ContentValues;
22import android.content.Context;
23import android.database.Cursor;
24import android.database.sqlite.SQLiteDatabase;
Dmitry Dementyev3f2d1712018-01-24 11:11:40 -080025import android.security.keystore.recovery.RecoveryController;
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -080026import android.text.TextUtils;
Dmitry Dementyev89f12d52019-02-28 12:26:01 -080027import android.util.ArrayMap;
Robert Berry76cf0832017-12-15 23:01:22 +000028import android.util.Log;
29
Bo Zhub10ba442018-03-31 17:13:46 -070030import com.android.server.locksettings.recoverablekeystore.TestOnlyInsecureCertificateHelper;
Robert Berry76cf0832017-12-15 23:01:22 +000031import com.android.server.locksettings.recoverablekeystore.WrappedKey;
32import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
Bo Zhu584b923f2017-12-22 16:05:15 -080033import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -070034import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry;
Robert Berrybc088402017-12-18 13:10:41 +000035import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
Robert Berry76cf0832017-12-15 23:01:22 +000036
Bo Zhu14d993d2018-02-03 21:38:48 -080037import java.io.ByteArrayInputStream;
Bo Zhu5b81fa62017-12-21 14:36:11 -080038import java.security.KeyFactory;
39import java.security.NoSuchAlgorithmException;
40import java.security.PublicKey;
Bo Zhu14d993d2018-02-03 21:38:48 -080041import java.security.cert.CertPath;
42import java.security.cert.CertificateEncodingException;
43import java.security.cert.CertificateException;
44import java.security.cert.CertificateFactory;
Bo Zhu5b81fa62017-12-21 14:36:11 -080045import java.security.spec.InvalidKeySpecException;
46import java.security.spec.X509EncodedKeySpec;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080047import java.util.ArrayList;
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -080048import java.util.Arrays;
Robert Berry76cf0832017-12-15 23:01:22 +000049import java.util.HashMap;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080050import java.util.List;
Robert Berry76cf0832017-12-15 23:01:22 +000051import java.util.Locale;
52import java.util.Map;
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -080053import java.util.StringJoiner;
Robert Berry76cf0832017-12-15 23:01:22 +000054
55/**
56 * Database of recoverable key information.
57 *
58 * @hide
59 */
60public class RecoverableKeyStoreDb {
61 private static final String TAG = "RecoverableKeyStoreDb";
62 private static final int IDLE_TIMEOUT_SECONDS = 30;
Robert Berryb7c06ea2017-12-21 13:37:23 +000063 private static final int LAST_SYNCED_AT_UNSYNCED = -1;
Bo Zhu14d993d2018-02-03 21:38:48 -080064 private static final String CERT_PATH_ENCODING = "PkiPath";
Robert Berry76cf0832017-12-15 23:01:22 +000065
66 private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
Bo Zhub10ba442018-03-31 17:13:46 -070067 private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
Robert Berry76cf0832017-12-15 23:01:22 +000068
69 /**
70 * A new instance, storing the database in the user directory of {@code context}.
71 *
72 * @hide
73 */
74 public static RecoverableKeyStoreDb newInstance(Context context) {
75 RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context);
76 helper.setWriteAheadLoggingEnabled(true);
77 helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS);
78 return new RecoverableKeyStoreDb(helper);
79 }
80
81 private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) {
82 this.mKeyStoreDbHelper = keyStoreDbHelper;
Bo Zhub10ba442018-03-31 17:13:46 -070083 this.mTestOnlyInsecureCertificateHelper = new TestOnlyInsecureCertificateHelper();
Robert Berry76cf0832017-12-15 23:01:22 +000084 }
85
86 /**
87 * Inserts a key into the database.
88 *
Robert Berryb7c06ea2017-12-21 13:37:23 +000089 * @param userId The uid of the profile the application is running under.
Robert Berry76cf0832017-12-15 23:01:22 +000090 * @param uid Uid of the application to whom the key belongs.
91 * @param alias The alias of the key in the AndroidKeyStore.
Robert Berry67b228c2017-12-18 19:26:22 +000092 * @param wrappedKey The wrapped key.
Robert Berry76cf0832017-12-15 23:01:22 +000093 * @return The primary key of the inserted row, or -1 if failed.
94 *
95 * @hide
96 */
Robert Berryb7c06ea2017-12-21 13:37:23 +000097 public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) {
Robert Berry76cf0832017-12-15 23:01:22 +000098 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
99 ContentValues values = new ContentValues();
Robert Berryb7c06ea2017-12-21 13:37:23 +0000100 values.put(KeysEntry.COLUMN_NAME_USER_ID, userId);
Robert Berry76cf0832017-12-15 23:01:22 +0000101 values.put(KeysEntry.COLUMN_NAME_UID, uid);
102 values.put(KeysEntry.COLUMN_NAME_ALIAS, alias);
103 values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce());
104 values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
Robert Berryb7c06ea2017-12-21 13:37:23 +0000105 values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED);
Robert Berry67b228c2017-12-18 19:26:22 +0000106 values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
Dmitry Dementyevad884712017-12-20 12:38:36 -0800107 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus());
Bo Zhu7ebcd662019-01-04 17:00:58 -0800108 byte[] keyMetadata = wrappedKey.getKeyMetadata();
109 if (keyMetadata == null) {
110 values.putNull(KeysEntry.COLUMN_NAME_KEY_METADATA);
111 } else {
112 values.put(KeysEntry.COLUMN_NAME_KEY_METADATA, keyMetadata);
113 }
Robert Berry76cf0832017-12-15 23:01:22 +0000114 return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
115 }
116
117 /**
118 * Gets the key with {@code alias} for the app with {@code uid}.
119 *
120 * @hide
121 */
122 @Nullable public WrappedKey getKey(int uid, String alias) {
123 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
124 String[] projection = {
125 KeysEntry._ID,
126 KeysEntry.COLUMN_NAME_NONCE,
127 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800128 KeysEntry.COLUMN_NAME_GENERATION_ID,
Bo Zhu7ebcd662019-01-04 17:00:58 -0800129 KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
130 KeysEntry.COLUMN_NAME_KEY_METADATA};
Robert Berry76cf0832017-12-15 23:01:22 +0000131 String selection =
132 KeysEntry.COLUMN_NAME_UID + " = ? AND "
133 + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
134 String[] selectionArguments = { Integer.toString(uid), alias };
135
136 try (
137 Cursor cursor = db.query(
138 KeysEntry.TABLE_NAME,
139 projection,
140 selection,
141 selectionArguments,
142 /*groupBy=*/ null,
143 /*having=*/ null,
144 /*orderBy=*/ null)
145 ) {
146 int count = cursor.getCount();
147 if (count == 0) {
148 return null;
149 }
150 if (count > 1) {
151 Log.wtf(TAG,
152 String.format(Locale.US,
153 "%d WrappedKey entries found for uid=%d alias='%s'. "
154 + "Should only ever be 0 or 1.", count, uid, alias));
155 return null;
156 }
157 cursor.moveToFirst();
158 byte[] nonce = cursor.getBlob(
159 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
160 byte[] keyMaterial = cursor.getBlob(
161 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
Robert Berry67b228c2017-12-18 19:26:22 +0000162 int generationId = cursor.getInt(
163 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
Dmitry Dementyevad884712017-12-20 12:38:36 -0800164 int recoveryStatus = cursor.getInt(
165 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
Bo Zhu7ebcd662019-01-04 17:00:58 -0800166
167 // Retrieve the metadata associated with the key
168 byte[] keyMetadata;
169 int metadataIdx = cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_KEY_METADATA);
170 if (cursor.isNull(metadataIdx)) {
171 keyMetadata = null;
172 } else {
173 keyMetadata = cursor.getBlob(metadataIdx);
174 }
175
176 return new WrappedKey(nonce, keyMaterial, keyMetadata, generationId, recoveryStatus);
Robert Berry76cf0832017-12-15 23:01:22 +0000177 }
178 }
179
180 /**
Robert Berry5daccec2018-01-06 19:16:25 +0000181 * Removes key with {@code alias} for app with {@code uid}.
182 *
183 * @return {@code true} if deleted a row.
184 */
185 public boolean removeKey(int uid, String alias) {
186 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
187 String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " +
188 KeysEntry.COLUMN_NAME_ALIAS + " = ?";
189 String[] selectionArgs = { Integer.toString(uid), alias };
190 return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0;
191 }
192
193 /**
Dmitry Dementyevad884712017-12-20 12:38:36 -0800194 * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}.
195 *
196 * @param uid of the application
197 *
198 * @return Map from Aliases to status.
199 *
200 * @hide
201 */
202 public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) {
203 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
204 String[] projection = {
205 KeysEntry._ID,
206 KeysEntry.COLUMN_NAME_ALIAS,
207 KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
208 String selection =
209 KeysEntry.COLUMN_NAME_UID + " = ?";
210 String[] selectionArguments = {Integer.toString(uid)};
211
212 try (
213 Cursor cursor = db.query(
214 KeysEntry.TABLE_NAME,
215 projection,
216 selection,
217 selectionArguments,
218 /*groupBy=*/ null,
219 /*having=*/ null,
220 /*orderBy=*/ null)
221 ) {
222 HashMap<String, Integer> statuses = new HashMap<>();
223 while (cursor.moveToNext()) {
224 String alias = cursor.getString(
225 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
226 int recoveryStatus = cursor.getInt(
227 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
228 statuses.put(alias, recoveryStatus);
229 }
230 return statuses;
231 }
232 }
233
234 /**
235 * Updates status for given key.
236 * @param uid of the application
237 * @param alias of the key
238 * @param status - new status
239 * @return number of updated entries.
240 * @hide
241 **/
242 public int setRecoveryStatus(int uid, String alias, int status) {
243 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
244 ContentValues values = new ContentValues();
245 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status);
246 String selection =
247 KeysEntry.COLUMN_NAME_UID + " = ? AND "
248 + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
249 return db.update(KeysEntry.TABLE_NAME, values, selection,
250 new String[] {String.valueOf(uid), alias});
251 }
252
253 /**
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800254 * Returns all keys for the given {@code userId} {@code recoveryAgentUid}
255 * and {@code platformKeyGenerationId}.
Robert Berry76cf0832017-12-15 23:01:22 +0000256 *
Robert Berryb7c06ea2017-12-21 13:37:23 +0000257 * @param userId User id of the profile to which all the keys are associated.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800258 * @param recoveryAgentUid Uid of the recovery agent which will perform the sync
Robert Berry76cf0832017-12-15 23:01:22 +0000259 * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys.
260 * (i.e., this should be the most recent generation ID, as older platform keys are not
261 * usable.)
262 *
263 * @hide
264 */
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800265 public @NonNull Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800266 int platformKeyGenerationId) {
Robert Berry76cf0832017-12-15 23:01:22 +0000267 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
268 String[] projection = {
269 KeysEntry._ID,
270 KeysEntry.COLUMN_NAME_NONCE,
271 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800272 KeysEntry.COLUMN_NAME_ALIAS,
Bo Zhu7ebcd662019-01-04 17:00:58 -0800273 KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
274 KeysEntry.COLUMN_NAME_KEY_METADATA};
Robert Berry76cf0832017-12-15 23:01:22 +0000275 String selection =
Robert Berryb7c06ea2017-12-21 13:37:23 +0000276 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800277 + KeysEntry.COLUMN_NAME_UID + " = ? AND "
Robert Berry76cf0832017-12-15 23:01:22 +0000278 + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
279 String[] selectionArguments = {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800280 Integer.toString(userId),
281 Integer.toString(recoveryAgentUid),
282 Integer.toString(platformKeyGenerationId)
283 };
Robert Berry76cf0832017-12-15 23:01:22 +0000284
285 try (
286 Cursor cursor = db.query(
287 KeysEntry.TABLE_NAME,
288 projection,
289 selection,
290 selectionArguments,
291 /*groupBy=*/ null,
292 /*having=*/ null,
293 /*orderBy=*/ null)
294 ) {
295 HashMap<String, WrappedKey> keys = new HashMap<>();
296 while (cursor.moveToNext()) {
297 byte[] nonce = cursor.getBlob(
298 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
299 byte[] keyMaterial = cursor.getBlob(
300 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
301 String alias = cursor.getString(
302 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
Dmitry Dementyevad884712017-12-20 12:38:36 -0800303 int recoveryStatus = cursor.getInt(
304 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
Bo Zhu7ebcd662019-01-04 17:00:58 -0800305
306 // Retrieve the metadata associated with the key
307 byte[] keyMetadata;
308 int metadataIdx = cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_KEY_METADATA);
309 if (cursor.isNull(metadataIdx)) {
310 keyMetadata = null;
311 } else {
312 keyMetadata = cursor.getBlob(metadataIdx);
313 }
314
315 keys.put(alias, new WrappedKey(nonce, keyMaterial, keyMetadata,
316 platformKeyGenerationId, recoveryStatus));
Robert Berry76cf0832017-12-15 23:01:22 +0000317 }
318 return keys;
319 }
320 }
321
322 /**
Dmitry Dementyev482633f2018-04-03 16:47:24 -0700323 * Sets the {@code generationId} of the platform key for user {@code userId}.
Robert Berrybc088402017-12-18 13:10:41 +0000324 *
325 * @return The primary key ID of the relation.
326 */
327 public long setPlatformKeyGenerationId(int userId, int generationId) {
328 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
329 ContentValues values = new ContentValues();
330 values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
331 values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
Dmitry Dementyev3f2d1712018-01-24 11:11:40 -0800332 long result = db.replace(
Robert Berrybc088402017-12-18 13:10:41 +0000333 UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
Dmitry Dementyev3f2d1712018-01-24 11:11:40 -0800334 if (result != -1) {
335 invalidateKeysWithOldGenerationId(userId, generationId);
336 }
337 return result;
338 }
339
340 /**
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800341 * Returns serial numbers associated with all known users.
342 * -1 is used for uninitialized serial numbers.
343 *
344 * See {@code UserHandle.getSerialNumberForUser}.
345 * @return Map from userId to serial numbers.
346 */
347 public @NonNull Map<Integer, Long> getUserSerialNumbers() {
348 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
349 String[] projection = {
350 UserMetadataEntry.COLUMN_NAME_USER_ID,
351 UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER};
352 String selection = null; // get all rows.
353 String[] selectionArguments = {};
354
355 try (
356 Cursor cursor = db.query(
357 UserMetadataEntry.TABLE_NAME,
358 projection,
359 selection,
360 selectionArguments,
361 /*groupBy=*/ null,
362 /*having=*/ null,
363 /*orderBy=*/ null)
364 ) {
365 Map<Integer, Long> serialNumbers = new ArrayMap<>();
366 while (cursor.moveToNext()) {
367 int userId = cursor.getInt(
368 cursor.getColumnIndexOrThrow(UserMetadataEntry.COLUMN_NAME_USER_ID));
369 long serialNumber = cursor.getLong(cursor.getColumnIndexOrThrow(
370 UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER));
371 serialNumbers.put(userId, serialNumber);
372 }
373 return serialNumbers;
374 }
375 }
376
377 /**
378 * Sets the {@code serialNumber} for the user {@code userId}.
379 *
380 * @return The primary key of the inserted row, or -1 if failed.
381 */
382 public long setUserSerialNumber(int userId, long serialNumber) {
383 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
384 ContentValues values = new ContentValues();
385 values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
386 values.put(UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER, serialNumber);
387 long result = db.replace(
388 UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
389 return result;
390 }
391
392 /**
Dmitry Dementyev3f2d1712018-01-24 11:11:40 -0800393 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
394 */
395 public void invalidateKeysWithOldGenerationId(int userId, int newGenerationId) {
396 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
397 ContentValues values = new ContentValues();
398 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
399 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
400 String selection =
401 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
402 + KeysEntry.COLUMN_NAME_GENERATION_ID + " < ?";
403 db.update(KeysEntry.TABLE_NAME, values, selection,
404 new String[] {String.valueOf(userId), String.valueOf(newGenerationId)});
Robert Berrybc088402017-12-18 13:10:41 +0000405 }
406
407 /**
Aseem Kumar3326da52018-03-12 18:05:16 -0700408 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
409 */
410 public void invalidateKeysForUserIdOnCustomScreenLock(int userId) {
411 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
412 ContentValues values = new ContentValues();
413 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
414 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
415 String selection =
416 KeysEntry.COLUMN_NAME_USER_ID + " = ?";
417 db.update(KeysEntry.TABLE_NAME, values, selection,
418 new String[] {String.valueOf(userId)});
419 }
420
421 /**
Robert Berrybc088402017-12-18 13:10:41 +0000422 * Returns the generation ID associated with the platform key of the user with {@code userId}.
423 */
424 public int getPlatformKeyGenerationId(int userId) {
425 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
426 String[] projection = {
427 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
428 String selection =
429 UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
430 String[] selectionArguments = {
431 Integer.toString(userId)};
432
433 try (
434 Cursor cursor = db.query(
435 UserMetadataEntry.TABLE_NAME,
436 projection,
437 selection,
438 selectionArguments,
439 /*groupBy=*/ null,
440 /*having=*/ null,
441 /*orderBy=*/ null)
442 ) {
443 if (cursor.getCount() == 0) {
444 return -1;
445 }
446 cursor.moveToFirst();
447 return cursor.getInt(
448 cursor.getColumnIndexOrThrow(
449 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
450 }
451 }
452
453 /**
Bo Zhu584b923f2017-12-22 16:05:15 -0800454 * Updates the public key of the recovery service into the database.
Bo Zhu5b81fa62017-12-21 14:36:11 -0800455 *
456 * @param userId The uid of the profile the application is running under.
457 * @param uid The uid of the application to whom the key belongs.
458 * @param publicKey The public key of the recovery service.
459 * @return The primary key of the inserted row, or -1 if failed.
460 *
461 * @hide
462 */
463 public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800464 return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY,
465 publicKey.getEncoded());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800466 }
467
468 /**
Bo Zhu14d993d2018-02-03 21:38:48 -0800469 * Returns the serial number of the XML file containing certificates of the recovery service.
470 *
471 * @param userId The userId of the profile the application is running under.
472 * @param uid The uid of the application who initializes the local recovery components.
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700473 * @param rootAlias The root of trust alias.
Bo Zhu14d993d2018-02-03 21:38:48 -0800474 * @return The value that were previously set, or null if there's none.
475 *
476 * @hide
477 */
478 @Nullable
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700479 public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) {
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800480 return getLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL);
Bo Zhu14d993d2018-02-03 21:38:48 -0800481 }
482
483 /**
484 * Records the serial number of the XML file containing certificates of the recovery service.
485 *
486 * @param userId The userId of the profile the application is running under.
487 * @param uid The uid of the application who initializes the local recovery components.
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700488 * @param rootAlias The root of trust alias.
Bo Zhu14d993d2018-02-03 21:38:48 -0800489 * @param serial The serial number contained in the XML file for recovery service certificates.
490 * @return The primary key of the inserted row, or -1 if failed.
491 *
492 * @hide
493 */
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700494 public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias,
495 long serial) {
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800496 return setLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700497 serial);
Bo Zhu14d993d2018-02-03 21:38:48 -0800498 }
499
500 /**
501 * Returns the {@code CertPath} of the recovery service.
502 *
503 * @param userId The userId of the profile the application is running under.
504 * @param uid The uid of the application who initializes the local recovery components.
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700505 * @param rootAlias The root of trust alias.
Bo Zhu14d993d2018-02-03 21:38:48 -0800506 * @return The value that were previously set, or null if there's none.
507 *
508 * @hide
509 */
510 @Nullable
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700511 public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) {
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800512 byte[] bytes = getBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH);
Bo Zhu14d993d2018-02-03 21:38:48 -0800513 if (bytes == null) {
514 return null;
515 }
516 try {
517 return decodeCertPath(bytes);
518 } catch (CertificateException e) {
519 Log.wtf(TAG,
520 String.format(Locale.US,
521 "Recovery service CertPath entry cannot be decoded for "
522 + "userId=%d uid=%d.",
523 userId, uid), e);
524 return null;
525 }
526 }
527
528 /**
529 * Sets the {@code CertPath} of the recovery service.
530 *
531 * @param userId The userId of the profile the application is running under.
532 * @param uid The uid of the application who initializes the local recovery components.
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700533 * @param rootAlias The root of trust alias.
Bo Zhu14d993d2018-02-03 21:38:48 -0800534 * @param certPath The certificate path of the recovery service.
535 * @return The primary key of the inserted row, or -1 if failed.
536 * @hide
537 */
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700538 public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias,
539 CertPath certPath) throws CertificateEncodingException {
Bo Zhu14d993d2018-02-03 21:38:48 -0800540 if (certPath.getCertificates().size() == 0) {
541 throw new CertificateEncodingException("No certificate contained in the cert path.");
542 }
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800543 return setBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH,
Bo Zhu14d993d2018-02-03 21:38:48 -0800544 certPath.getEncoded(CERT_PATH_ENCODING));
545 }
546
547 /**
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800548 * Returns the list of recovery agents initialized for given {@code userId}
549 * @param userId The userId of the profile the application is running under.
550 * @return The list of recovery agents
551 * @hide
Robert Berry91044042017-12-27 12:05:58 +0000552 */
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800553 public @NonNull List<Integer> getRecoveryAgents(int userId) {
Robert Berry91044042017-12-27 12:05:58 +0000554 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
555
556 String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID };
557 String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
558 String[] selectionArguments = { Integer.toString(userId) };
559
560 try (
561 Cursor cursor = db.query(
562 RecoveryServiceMetadataEntry.TABLE_NAME,
563 projection,
564 selection,
565 selectionArguments,
566 /*groupBy=*/ null,
567 /*having=*/ null,
568 /*orderBy=*/ null)
569 ) {
570 int count = cursor.getCount();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800571 ArrayList<Integer> result = new ArrayList<>(count);
572 while (cursor.moveToNext()) {
573 int uid = cursor.getInt(
574 cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID));
575 result.add(uid);
Robert Berry91044042017-12-27 12:05:58 +0000576 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800577 return result;
Robert Berry91044042017-12-27 12:05:58 +0000578 }
579 }
580
581 /**
Bo Zhu5b81fa62017-12-21 14:36:11 -0800582 * Returns the public key of the recovery service.
583 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800584 * @param userId The userId of the profile the application is running under.
Bo Zhu5b81fa62017-12-21 14:36:11 -0800585 * @param uid The uid of the application who initializes the local recovery components.
586 *
587 * @hide
588 */
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000589 @Nullable
Bo Zhu5b81fa62017-12-21 14:36:11 -0800590 public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800591 byte[] keyBytes =
592 getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
593 if (keyBytes == null) {
594 return null;
595 }
596 try {
597 return decodeX509Key(keyBytes);
598 } catch (InvalidKeySpecException e) {
599 Log.wtf(TAG,
600 String.format(Locale.US,
601 "Recovery service public key entry cannot be decoded for "
602 + "userId=%d uid=%d.",
603 userId, uid));
604 return null;
Bo Zhu5b81fa62017-12-21 14:36:11 -0800605 }
606 }
607
608 /**
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800609 * Updates the list of user secret types used for end-to-end encryption.
610 * If no secret types are set, recovery snapshot will not be created.
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800611 * See {@code KeyChainProtectionParams}
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800612 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800613 * @param userId The userId of the profile the application is running under.
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800614 * @param uid The uid of the application.
615 * @param secretTypes list of secret types
616 * @return The primary key of the updated row, or -1 if failed.
617 *
618 * @hide
619 */
620 public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) {
621 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
622 ContentValues values = new ContentValues();
623 StringJoiner joiner = new StringJoiner(",");
624 Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i)));
625 String typesAsCsv = joiner.toString();
626 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv);
627 String selection =
628 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
629 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
630 ensureRecoveryServiceMetadataEntryExists(userId, uid);
631 return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection,
632 new String[] {String.valueOf(userId), String.valueOf(uid)});
633 }
634
635 /**
636 * Returns the list of secret types used for end-to-end encryption.
637 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800638 * @param userId The userId of the profile the application is running under.
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800639 * @param uid The uid of the application who initialized the local recovery components.
640 * @return Secret types or empty array, if types were not set.
641 *
642 * @hide
643 */
644 public @NonNull int[] getRecoverySecretTypes(int userId, int uid) {
645 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
646
647 String[] projection = {
648 RecoveryServiceMetadataEntry._ID,
649 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
650 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
651 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES};
652 String selection =
653 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
654 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
655 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
656
657 try (
658 Cursor cursor = db.query(
659 RecoveryServiceMetadataEntry.TABLE_NAME,
660 projection,
661 selection,
662 selectionArguments,
663 /*groupBy=*/ null,
664 /*having=*/ null,
665 /*orderBy=*/ null)
666 ) {
667 int count = cursor.getCount();
668 if (count == 0) {
669 return new int[]{};
670 }
671 if (count > 1) {
672 Log.wtf(TAG,
673 String.format(Locale.US,
674 "%d deviceId entries found for userId=%d uid=%d. "
675 + "Should only ever be 0 or 1.", count, userId, uid));
676 return new int[]{};
677 }
678 cursor.moveToFirst();
679 int idx = cursor.getColumnIndexOrThrow(
680 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES);
681 if (cursor.isNull(idx)) {
682 return new int[]{};
683 }
684 String csv = cursor.getString(idx);
685 if (TextUtils.isEmpty(csv)) {
686 return new int[]{};
687 }
688 String[] types = csv.split(",");
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000689 int[] result = new int[types.length];
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800690 for (int i = 0; i < types.length; i++) {
691 try {
692 result[i] = Integer.parseInt(types[i]);
693 } catch (NumberFormatException e) {
694 Log.wtf(TAG, "String format error " + e);
695 }
696 }
697 return result;
698 }
699 }
700
701 /**
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700702 * Active root of trust for the recovery agent.
703 *
704 * @param userId The userId of the profile the application is running under.
705 * @param uid The uid of the application.
706 * @param rootAlias The root of trust alias.
707 * @return The primary key of the updated row, or -1 if failed.
708 *
709 * @hide
710 */
711 public long setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias) {
712 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
713 ContentValues values = new ContentValues();
714 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, rootAlias);
715 String selection =
716 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
717 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
718 ensureRecoveryServiceMetadataEntryExists(userId, uid);
719 return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values,
720 selection, new String[] {String.valueOf(userId), String.valueOf(uid)});
721 }
722
723 /**
724 * Active root of trust for the recovery agent.
725 *
726 * @param userId The userId of the profile the application is running under.
727 * @param uid The uid of the application who initialized the local recovery components.
728 * @return Active root of trust alias of null if it was not set
729 *
730 * @hide
731 */
732 public @Nullable String getActiveRootOfTrust(int userId, int uid) {
733 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
734
735 String[] projection = {
736 RecoveryServiceMetadataEntry._ID,
737 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
738 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
739 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST};
740 String selection =
741 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
742 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
743 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
744
745 try (
746 Cursor cursor = db.query(
747 RecoveryServiceMetadataEntry.TABLE_NAME,
748 projection,
749 selection,
750 selectionArguments,
751 /*groupBy=*/ null,
752 /*having=*/ null,
753 /*orderBy=*/ null)
754 ) {
755 int count = cursor.getCount();
756 if (count == 0) {
757 return null;
758 }
759 if (count > 1) {
760 Log.wtf(TAG,
761 String.format(Locale.US,
762 "%d deviceId entries found for userId=%d uid=%d. "
763 + "Should only ever be 0 or 1.", count, userId, uid));
764 return null;
765 }
766 cursor.moveToFirst();
767 int idx = cursor.getColumnIndexOrThrow(
768 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST);
769 if (cursor.isNull(idx)) {
770 return null;
771 }
772 String result = cursor.getString(idx);
773 if (TextUtils.isEmpty(result)) {
774 return null;
775 }
776 return result;
777 }
778 }
779
780 /**
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800781 * Updates the counterId
782 *
783 * @param userId The userId of the profile the application is running under.
784 * @param uid The uid of the application.
785 * @param counterId The counterId.
786 * @return The primary key of the inserted row, or -1 if failed.
787 *
788 * @hide
789 */
790 public long setCounterId(int userId, int uid, long counterId) {
791 return setLong(userId, uid,
792 RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, counterId);
793 }
794
795 /**
796 * Returns the counter id.
797 *
798 * @param userId The userId of the profile the application is running under.
799 * @param uid The uid of the application who initialized the local recovery components.
800 * @return The counter id
801 *
802 * @hide
803 */
804 @Nullable
805 public Long getCounterId(int userId, int uid) {
806 return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID);
807 }
808
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800809 /**
Bo Zhu584b923f2017-12-22 16:05:15 -0800810 * Updates the server parameters given by the application initializing the local recovery
811 * components.
812 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800813 * @param userId The userId of the profile the application is running under.
Bo Zhu584b923f2017-12-22 16:05:15 -0800814 * @param uid The uid of the application.
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800815 * @param serverParams The server parameters.
Bo Zhu584b923f2017-12-22 16:05:15 -0800816 * @return The primary key of the inserted row, or -1 if failed.
817 *
818 * @hide
819 */
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800820 public long setServerParams(int userId, int uid, byte[] serverParams) {
821 return setBytes(userId, uid,
822 RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams);
Bo Zhu584b923f2017-12-22 16:05:15 -0800823 }
824
825 /**
826 * Returns the server paramters that was previously set by the application who initialized the
827 * local recovery service components.
828 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800829 * @param userId The userId of the profile the application is running under.
Bo Zhu584b923f2017-12-22 16:05:15 -0800830 * @param uid The uid of the application who initialized the local recovery components.
831 * @return The server parameters that were previously set, or null if there's none.
832 *
833 * @hide
834 */
Bo Zhu57e77f72018-01-03 14:49:43 -0800835 @Nullable
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800836 public byte[] getServerParams(int userId, int uid) {
837 return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800838 }
839
840 /**
841 * Updates the snapshot version.
842 *
843 * @param userId The userId of the profile the application is running under.
844 * @param uid The uid of the application.
845 * @param snapshotVersion The snapshot version
846 * @return The primary key of the inserted row, or -1 if failed.
847 *
848 * @hide
849 */
850 public long setSnapshotVersion(int userId, int uid, long snapshotVersion) {
851 return setLong(userId, uid,
852 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION, snapshotVersion);
853 }
854
855 /**
856 * Returns the snapshot version
857 *
858 * @param userId The userId of the profile the application is running under.
859 * @param uid The uid of the application who initialized the local recovery components.
860 * @return The server parameters that were previously set, or null if there's none.
861 *
862 * @hide
863 */
864 @Nullable
865 public Long getSnapshotVersion(int userId, int uid) {
866 return getLong(userId, uid,
867 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION);
868 }
869
870 /**
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700871 * Updates a flag indicating that a new snapshot should be created.
872 * It will be {@code false} until the first application key is added.
873 * After that, the flag will be set to true, if one of the following values is updated:
874 * <ul>
875 * <li> List of application keys
876 * <li> Server params.
877 * <li> Lock-screen secret.
878 * <li> Lock-screen secret type.
879 * <li> Trusted hardware certificate.
880 * </ul>
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800881 *
882 * @param userId The userId of the profile the application is running under.
883 * @param uid The uid of the application.
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700884 * @param pending Should create snapshot flag.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800885 * @return The primary key of the inserted row, or -1 if failed.
886 *
887 * @hide
888 */
889 public long setShouldCreateSnapshot(int userId, int uid, boolean pending) {
890 return setLong(userId, uid,
891 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT, pending ? 1 : 0);
892 }
893
894 /**
895 * Returns {@code true} if new snapshot should be created.
896 * Returns {@code false} if the flag was never set.
897 *
898 * @param userId The userId of the profile the application is running under.
899 * @param uid The uid of the application who initialized the local recovery components.
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700900 * @return should create snapshot flag
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800901 *
902 * @hide
903 */
904 public boolean getShouldCreateSnapshot(int userId, int uid) {
905 Long res = getLong(userId, uid,
906 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT);
907 return res != null && res != 0L;
908 }
909
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800910
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800911 /**
912 * Returns given long value from the database.
913 *
914 * @param userId The userId of the profile the application is running under.
915 * @param uid The uid of the application who initialized the local recovery components.
916 * @param key from {@code RecoveryServiceMetadataEntry}
917 * @return The value that were previously set, or null if there's none.
918 *
919 * @hide
920 */
921 private Long getLong(int userId, int uid, String key) {
Bo Zhu584b923f2017-12-22 16:05:15 -0800922 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
923
924 String[] projection = {
925 RecoveryServiceMetadataEntry._ID,
926 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
927 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800928 key};
Bo Zhu584b923f2017-12-22 16:05:15 -0800929 String selection =
930 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
931 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
932 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
933
934 try (
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800935 Cursor cursor = db.query(
936 RecoveryServiceMetadataEntry.TABLE_NAME,
937 projection,
938 selection,
939 selectionArguments,
940 /*groupBy=*/ null,
941 /*having=*/ null,
942 /*orderBy=*/ null)
Bo Zhu584b923f2017-12-22 16:05:15 -0800943 ) {
944 int count = cursor.getCount();
945 if (count == 0) {
946 return null;
947 }
948 if (count > 1) {
949 Log.wtf(TAG,
950 String.format(Locale.US,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800951 "%d entries found for userId=%d uid=%d. "
Bo Zhu584b923f2017-12-22 16:05:15 -0800952 + "Should only ever be 0 or 1.", count, userId, uid));
953 return null;
954 }
955 cursor.moveToFirst();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800956 int idx = cursor.getColumnIndexOrThrow(key);
Bo Zhu584b923f2017-12-22 16:05:15 -0800957 if (cursor.isNull(idx)) {
958 return null;
959 } else {
960 return cursor.getLong(idx);
961 }
962 }
963 }
964
965 /**
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800966 * Sets a long value in the database.
967 *
968 * @param userId The userId of the profile the application is running under.
969 * @param uid The uid of the application who initialized the local recovery components.
970 * @param key defined in {@code RecoveryServiceMetadataEntry}
971 * @param value new value.
972 * @return The primary key of the inserted row, or -1 if failed.
973 *
974 * @hide
975 */
976
977 private long setLong(int userId, int uid, String key, long value) {
978 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
979 ContentValues values = new ContentValues();
980 values.put(key, value);
981 String selection =
982 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
983 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
984 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
985
986 ensureRecoveryServiceMetadataEntryExists(userId, uid);
987 return db.update(
988 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
989 }
990
991 /**
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800992 * Returns given binary value from the database.
993 *
994 * @param userId The userId of the profile the application is running under.
995 * @param uid The uid of the application who initialized the local recovery components.
996 * @param key from {@code RecoveryServiceMetadataEntry}
997 * @return The value that were previously set, or null if there's none.
998 *
999 * @hide
1000 */
1001 private byte[] getBytes(int userId, int uid, String key) {
1002 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
1003
1004 String[] projection = {
1005 RecoveryServiceMetadataEntry._ID,
1006 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
1007 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
1008 key};
1009 String selection =
1010 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
1011 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
1012 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
1013
1014 try (
1015 Cursor cursor = db.query(
1016 RecoveryServiceMetadataEntry.TABLE_NAME,
1017 projection,
1018 selection,
1019 selectionArguments,
1020 /*groupBy=*/ null,
1021 /*having=*/ null,
1022 /*orderBy=*/ null)
1023 ) {
1024 int count = cursor.getCount();
1025 if (count == 0) {
1026 return null;
1027 }
1028 if (count > 1) {
1029 Log.wtf(TAG,
1030 String.format(Locale.US,
1031 "%d entries found for userId=%d uid=%d. "
1032 + "Should only ever be 0 or 1.", count, userId, uid));
1033 return null;
1034 }
1035 cursor.moveToFirst();
1036 int idx = cursor.getColumnIndexOrThrow(key);
1037 if (cursor.isNull(idx)) {
1038 return null;
1039 } else {
1040 return cursor.getBlob(idx);
1041 }
1042 }
1043 }
1044
1045 /**
1046 * Sets a binary value in the database.
1047 *
1048 * @param userId The userId of the profile the application is running under.
1049 * @param uid The uid of the application who initialized the local recovery components.
1050 * @param key defined in {@code RecoveryServiceMetadataEntry}
1051 * @param value new value.
1052 * @return The primary key of the inserted row, or -1 if failed.
1053 *
1054 * @hide
1055 */
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -08001056 private long setBytes(int userId, int uid, String key, byte[] value) {
1057 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1058 ContentValues values = new ContentValues();
1059 values.put(key, value);
1060 String selection =
1061 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
1062 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
1063 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
1064
1065 ensureRecoveryServiceMetadataEntryExists(userId, uid);
1066 return db.update(
1067 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
1068 }
1069
1070 /**
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001071 * Returns given binary value from the database.
1072 *
1073 * @param userId The userId of the profile the application is running under.
1074 * @param uid The uid of the application who initialized the local recovery components.
1075 * @param rootAlias The root of trust alias.
1076 * @param key from {@code RootOfTrustEntry}
1077 * @return The value that were previously set, or null if there's none.
1078 *
1079 * @hide
1080 */
1081 private byte[] getBytes(int userId, int uid, String rootAlias, String key) {
Bo Zhub10ba442018-03-31 17:13:46 -07001082 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001083 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
1084
1085 String[] projection = {
1086 RootOfTrustEntry._ID,
1087 RootOfTrustEntry.COLUMN_NAME_USER_ID,
1088 RootOfTrustEntry.COLUMN_NAME_UID,
1089 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
1090 key};
1091 String selection =
1092 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1093 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1094 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1095 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1096
1097 try (
1098 Cursor cursor = db.query(
1099 RootOfTrustEntry.TABLE_NAME,
1100 projection,
1101 selection,
1102 selectionArguments,
1103 /*groupBy=*/ null,
1104 /*having=*/ null,
1105 /*orderBy=*/ null)
1106 ) {
1107 int count = cursor.getCount();
1108 if (count == 0) {
1109 return null;
1110 }
1111 if (count > 1) {
1112 Log.wtf(TAG,
1113 String.format(Locale.US,
1114 "%d entries found for userId=%d uid=%d. "
1115 + "Should only ever be 0 or 1.", count, userId, uid));
1116 return null;
1117 }
1118 cursor.moveToFirst();
1119 int idx = cursor.getColumnIndexOrThrow(key);
1120 if (cursor.isNull(idx)) {
1121 return null;
1122 } else {
1123 return cursor.getBlob(idx);
1124 }
1125 }
1126 }
1127
1128 /**
1129 * Sets a binary value in the database.
1130 *
1131 * @param userId The userId of the profile the application is running under.
1132 * @param uid The uid of the application who initialized the local recovery components.
1133 * @param rootAlias The root of trust alias.
1134 * @param key defined in {@code RootOfTrustEntry}
1135 * @param value new value.
1136 * @return The primary key of the inserted row, or -1 if failed.
1137 *
1138 * @hide
1139 */
1140 private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) {
Bo Zhub10ba442018-03-31 17:13:46 -07001141 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001142 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1143 ContentValues values = new ContentValues();
1144 values.put(key, value);
1145 String selection =
1146 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1147 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1148 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1149 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1150
1151 ensureRootOfTrustEntryExists(userId, uid, rootAlias);
1152 return db.update(
1153 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
1154 }
1155
1156 /**
1157 * Returns given long value from the database.
1158 *
1159 * @param userId The userId of the profile the application is running under.
1160 * @param uid The uid of the application who initialized the local recovery components.
1161 * @param rootAlias The root of trust alias.
1162 * @param key from {@code RootOfTrustEntry}
1163 * @return The value that were previously set, or null if there's none.
1164 *
1165 * @hide
1166 */
1167 private Long getLong(int userId, int uid, String rootAlias, String key) {
Bo Zhub10ba442018-03-31 17:13:46 -07001168 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001169 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
1170
1171 String[] projection = {
1172 RootOfTrustEntry._ID,
1173 RootOfTrustEntry.COLUMN_NAME_USER_ID,
1174 RootOfTrustEntry.COLUMN_NAME_UID,
1175 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
1176 key};
1177 String selection =
1178 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1179 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1180 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1181 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1182
1183 try (
1184 Cursor cursor = db.query(
1185 RootOfTrustEntry.TABLE_NAME,
1186 projection,
1187 selection,
1188 selectionArguments,
1189 /*groupBy=*/ null,
1190 /*having=*/ null,
1191 /*orderBy=*/ null)
1192 ) {
1193 int count = cursor.getCount();
1194 if (count == 0) {
1195 return null;
1196 }
1197 if (count > 1) {
1198 Log.wtf(TAG,
1199 String.format(Locale.US,
1200 "%d entries found for userId=%d uid=%d. "
1201 + "Should only ever be 0 or 1.", count, userId, uid));
1202 return null;
1203 }
1204 cursor.moveToFirst();
1205 int idx = cursor.getColumnIndexOrThrow(key);
1206 if (cursor.isNull(idx)) {
1207 return null;
1208 } else {
1209 return cursor.getLong(idx);
1210 }
1211 }
1212 }
1213
1214 /**
1215 * Sets a long value in the database.
1216 *
1217 * @param userId The userId of the profile the application is running under.
1218 * @param uid The uid of the application who initialized the local recovery components.
1219 * @param rootAlias The root of trust alias.
1220 * @param key defined in {@code RootOfTrustEntry}
1221 * @param value new value.
1222 * @return The primary key of the inserted row, or -1 if failed.
1223 *
1224 * @hide
1225 */
1226
1227 private long setLong(int userId, int uid, String rootAlias, String key, long value) {
Bo Zhub10ba442018-03-31 17:13:46 -07001228 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001229 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1230 ContentValues values = new ContentValues();
1231 values.put(key, value);
1232 String selection =
1233 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1234 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1235 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1236 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1237
1238 ensureRootOfTrustEntryExists(userId, uid, rootAlias);
1239 return db.update(
1240 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
1241 }
1242
Dmitry Dementyev89f12d52019-02-28 12:26:01 -08001243 /**
1244 * Removes all entries for given {@code userId}.
1245 */
1246 public void removeUserFromAllTables(int userId) {
1247 removeUserFromKeysTable(userId);
1248 removeUserFromUserMetadataTable(userId);
1249 removeUserFromRecoveryServiceMetadataTable(userId);
1250 removeUserFromRootOfTrustTable(userId);
1251 }
1252
1253 /**
1254 * Removes all entries for given userId from Keys table.
1255 *
1256 * @return {@code true} if deleted a row.
1257 */
1258 private boolean removeUserFromKeysTable(int userId) {
1259 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1260 String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?";
1261 String[] selectionArgs = {Integer.toString(userId)};
1262 return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0;
1263 }
1264
1265 /**
1266 * Removes all entries for given userId from UserMetadata table.
1267 *
1268 * @return {@code true} if deleted a row.
1269 */
1270 private boolean removeUserFromUserMetadataTable(int userId) {
1271 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1272 String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
1273 String[] selectionArgs = {Integer.toString(userId)};
1274 return db.delete(UserMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0;
1275 }
1276
1277 /**
1278 * Removes all entries for given userId from RecoveryServiceMetadata table.
1279 *
1280 * @return {@code true} if deleted a row.
1281 */
1282 private boolean removeUserFromRecoveryServiceMetadataTable(int userId) {
1283 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1284 String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
1285 String[] selectionArgs = {Integer.toString(userId)};
1286 return db.delete(RecoveryServiceMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0;
1287 }
1288
1289 /**
1290 * Removes all entries for given userId from RootOfTrust table.
1291 *
1292 * @return {@code true} if deleted a row.
1293 */
1294 private boolean removeUserFromRootOfTrustTable(int userId) {
1295 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1296 String selection = RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ?";
1297 String[] selectionArgs = {Integer.toString(userId)};
1298 return db.delete(RootOfTrustEntry.TABLE_NAME, selection, selectionArgs) > 0;
1299 }
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001300
1301 /**
Bo Zhu584b923f2017-12-22 16:05:15 -08001302 * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
1303 * the given userId and uid, so db.update will succeed.
1304 */
1305 private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
1306 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1307 ContentValues values = new ContentValues();
1308 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
1309 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
1310 db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
1311 values, SQLiteDatabase.CONFLICT_IGNORE);
1312 }
1313
1314 /**
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -07001315 * Creates an empty row in the root of trust table if such a row doesn't exist for
1316 * the given userId and uid, so db.update will succeed.
1317 */
1318 private void ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias) {
1319 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1320 ContentValues values = new ContentValues();
1321 values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, userId);
1322 values.put(RootOfTrustEntry.COLUMN_NAME_UID, uid);
1323 values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, rootAlias);
1324 db.insertWithOnConflict(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null,
1325 values, SQLiteDatabase.CONFLICT_IGNORE);
1326 }
1327
1328 /**
Robert Berry76cf0832017-12-15 23:01:22 +00001329 * Closes all open connections to the database.
1330 */
1331 public void close() {
1332 mKeyStoreDbHelper.close();
1333 }
1334
Robert Berryaa3f4ca2017-12-27 10:53:58 +00001335 @Nullable
1336 private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException {
1337 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
1338 try {
1339 return KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
1340 } catch (NoSuchAlgorithmException e) {
1341 // Should never happen
1342 throw new RuntimeException(e);
1343 }
1344 }
Bo Zhu14d993d2018-02-03 21:38:48 -08001345
1346 @Nullable
1347 private static CertPath decodeCertPath(byte[] bytes) throws CertificateException {
1348 CertificateFactory certFactory;
1349 try {
1350 certFactory = CertificateFactory.getInstance("X.509");
1351 } catch (CertificateException e) {
1352 // Should not happen, as X.509 is mandatory for all providers.
1353 throw new RuntimeException(e);
1354 }
1355 return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING);
1356 }
Robert Berry76cf0832017-12-15 23:01:22 +00001357}