1 /* 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 17 package com.android.server.locksettings.recoverablekeystore.storage; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.security.keystore.recovery.RecoveryController; 26 import android.text.TextUtils; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import com.android.server.locksettings.recoverablekeystore.TestOnlyInsecureCertificateHelper; 31 import com.android.server.locksettings.recoverablekeystore.WrappedKey; 32 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; 33 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry; 34 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry; 35 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; 36 37 import java.io.ByteArrayInputStream; 38 import java.security.KeyFactory; 39 import java.security.NoSuchAlgorithmException; 40 import java.security.PublicKey; 41 import java.security.cert.CertPath; 42 import java.security.cert.CertificateEncodingException; 43 import java.security.cert.CertificateException; 44 import java.security.cert.CertificateFactory; 45 import java.security.spec.InvalidKeySpecException; 46 import java.security.spec.X509EncodedKeySpec; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Locale; 52 import java.util.Map; 53 import java.util.StringJoiner; 54 55 /** 56 * Database of recoverable key information. 57 * 58 * @hide 59 */ 60 public class RecoverableKeyStoreDb { 61 private static final String TAG = "RecoverableKeyStoreDb"; 62 private static final int IDLE_TIMEOUT_SECONDS = 30; 63 private static final int LAST_SYNCED_AT_UNSYNCED = -1; 64 private static final String CERT_PATH_ENCODING = "PkiPath"; 65 66 private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper; 67 private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; 68 69 /** 70 * A new instance, storing the database in the user directory of {@code context}. 71 * 72 * @hide 73 */ newInstance(Context context)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 RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper)81 private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { 82 this.mKeyStoreDbHelper = keyStoreDbHelper; 83 this.mTestOnlyInsecureCertificateHelper = new TestOnlyInsecureCertificateHelper(); 84 } 85 86 /** 87 * Inserts a key into the database. 88 * 89 * @param userId The uid of the profile the application is running under. 90 * @param uid Uid of the application to whom the key belongs. 91 * @param alias The alias of the key in the AndroidKeyStore. 92 * @param wrappedKey The wrapped key. 93 * @return The primary key of the inserted row, or -1 if failed. 94 * 95 * @hide 96 */ insertKey(int userId, int uid, String alias, WrappedKey wrappedKey)97 public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) { 98 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 99 ContentValues values = new ContentValues(); 100 values.put(KeysEntry.COLUMN_NAME_USER_ID, userId); 101 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()); 105 values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED); 106 values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); 107 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus()); 108 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 } 114 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 */ getKey(int uid, String alias)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, 128 KeysEntry.COLUMN_NAME_GENERATION_ID, 129 KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 130 KeysEntry.COLUMN_NAME_KEY_METADATA}; 131 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)); 162 int generationId = cursor.getInt( 163 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID)); 164 int recoveryStatus = cursor.getInt( 165 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); 166 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); 177 } 178 } 179 180 /** 181 * Removes key with {@code alias} for app with {@code uid}. 182 * 183 * @return {@code true} if deleted a row. 184 */ removeKey(int uid, String alias)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 /** 194 * 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 */ getStatusForAllKeys(int uid)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 **/ setRecoveryStatus(int uid, String alias, int status)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 /** 254 * Returns all keys for the given {@code userId} {@code recoveryAgentUid} 255 * and {@code platformKeyGenerationId}. 256 * 257 * @param userId User id of the profile to which all the keys are associated. 258 * @param recoveryAgentUid Uid of the recovery agent which will perform the sync 259 * @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 */ getAllKeys(int userId, int recoveryAgentUid, int platformKeyGenerationId)265 public @NonNull Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid, 266 int platformKeyGenerationId) { 267 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 268 String[] projection = { 269 KeysEntry._ID, 270 KeysEntry.COLUMN_NAME_NONCE, 271 KeysEntry.COLUMN_NAME_WRAPPED_KEY, 272 KeysEntry.COLUMN_NAME_ALIAS, 273 KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 274 KeysEntry.COLUMN_NAME_KEY_METADATA}; 275 String selection = 276 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND " 277 + KeysEntry.COLUMN_NAME_UID + " = ? AND " 278 + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; 279 String[] selectionArguments = { 280 Integer.toString(userId), 281 Integer.toString(recoveryAgentUid), 282 Integer.toString(platformKeyGenerationId) 283 }; 284 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)); 303 int recoveryStatus = cursor.getInt( 304 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); 305 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)); 317 } 318 return keys; 319 } 320 } 321 322 /** 323 * Sets the {@code generationId} of the platform key for user {@code userId}. 324 * 325 * @return The number of updated rows. 326 */ setPlatformKeyGenerationId(int userId, int generationId)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); 332 String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 333 String[] selectionArguments = new String[] {String.valueOf(userId)}; 334 335 ensureUserMetadataEntryExists(userId); 336 invalidateKeysForUser(userId); 337 return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments); 338 } 339 340 /** 341 * 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 */ getUserSerialNumbers()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 number of updated rows. 381 */ setUserSerialNumber(int userId, long serialNumber)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 String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 388 String[] selectionArguments = new String[] {String.valueOf(userId)}; 389 390 ensureUserMetadataEntryExists(userId); 391 return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments); 392 } 393 394 /** 395 * Sets the {@code badGuessCounter} for the user {@code userId}. 396 * 397 * @return The number of updated rows. 398 */ setBadRemoteGuessCounter(int userId, int badGuessCounter)399 public long setBadRemoteGuessCounter(int userId, int badGuessCounter) { 400 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 401 ContentValues values = new ContentValues(); 402 values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); 403 values.put(UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER, badGuessCounter); 404 String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 405 String[] selectionArguments = new String[] {String.valueOf(userId)}; 406 407 ensureUserMetadataEntryExists(userId); 408 return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments); 409 } 410 411 /** 412 * Returns the number of invalid remote lock screen guesses for the user {@code userId}. 413 */ getBadRemoteGuessCounter(int userId)414 public int getBadRemoteGuessCounter(int userId) { 415 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 416 String[] projection = { 417 UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER}; 418 String selection = 419 UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 420 String[] selectionArguments = { 421 Integer.toString(userId)}; 422 423 try ( 424 Cursor cursor = db.query( 425 UserMetadataEntry.TABLE_NAME, 426 projection, 427 selection, 428 selectionArguments, 429 /*groupBy=*/ null, 430 /*having=*/ null, 431 /*orderBy=*/ null) 432 ) { 433 if (cursor.getCount() == 0) { 434 return 0; 435 } 436 cursor.moveToFirst(); 437 return cursor.getInt( 438 cursor.getColumnIndexOrThrow( 439 UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER)); 440 } 441 } 442 443 /** 444 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 445 */ invalidateKeysForUser(int userId)446 public void invalidateKeysForUser(int userId) { 447 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 448 ContentValues values = new ContentValues(); 449 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 450 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); 451 String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?"; 452 db.update(KeysEntry.TABLE_NAME, values, selection, new String[] {String.valueOf(userId)}); 453 } 454 455 /** 456 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 457 */ invalidateKeysForUserIdOnCustomScreenLock(int userId)458 public void invalidateKeysForUserIdOnCustomScreenLock(int userId) { 459 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 460 ContentValues values = new ContentValues(); 461 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 462 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); 463 String selection = 464 KeysEntry.COLUMN_NAME_USER_ID + " = ?"; 465 db.update(KeysEntry.TABLE_NAME, values, selection, 466 new String[] {String.valueOf(userId)}); 467 } 468 469 /** 470 * Returns the generation ID associated with the platform key of the user with {@code userId}. 471 */ getPlatformKeyGenerationId(int userId)472 public int getPlatformKeyGenerationId(int userId) { 473 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 474 String[] projection = { 475 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID}; 476 String selection = 477 UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 478 String[] selectionArguments = { 479 Integer.toString(userId)}; 480 481 try ( 482 Cursor cursor = db.query( 483 UserMetadataEntry.TABLE_NAME, 484 projection, 485 selection, 486 selectionArguments, 487 /*groupBy=*/ null, 488 /*having=*/ null, 489 /*orderBy=*/ null) 490 ) { 491 if (cursor.getCount() == 0) { 492 return -1; 493 } 494 cursor.moveToFirst(); 495 return cursor.getInt( 496 cursor.getColumnIndexOrThrow( 497 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID)); 498 } 499 } 500 501 /** 502 * Updates the public key of the recovery service into the database. 503 * 504 * @param userId The uid of the profile the application is running under. 505 * @param uid The uid of the application to whom the key belongs. 506 * @param publicKey The public key of the recovery service. 507 * @return The primary key of the inserted row, or -1 if failed. 508 * 509 * @hide 510 */ setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey)511 public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) { 512 return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, 513 publicKey.getEncoded()); 514 } 515 516 /** 517 * Returns the serial number of the XML file containing certificates of the recovery service. 518 * 519 * @param userId The userId of the profile the application is running under. 520 * @param uid The uid of the application who initializes the local recovery components. 521 * @param rootAlias The root of trust alias. 522 * @return The value that were previously set, or null if there's none. 523 * 524 * @hide 525 */ 526 @Nullable getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias)527 public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) { 528 return getLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL); 529 } 530 531 /** 532 * Records the serial number of the XML file containing certificates of the recovery service. 533 * 534 * @param userId The userId of the profile the application is running under. 535 * @param uid The uid of the application who initializes the local recovery components. 536 * @param rootAlias The root of trust alias. 537 * @param serial The serial number contained in the XML file for recovery service certificates. 538 * @return The primary key of the inserted row, or -1 if failed. 539 * 540 * @hide 541 */ setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias, long serial)542 public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias, 543 long serial) { 544 return setLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL, 545 serial); 546 } 547 548 /** 549 * Returns the {@code CertPath} of the recovery service. 550 * 551 * @param userId The userId of the profile the application is running under. 552 * @param uid The uid of the application who initializes the local recovery components. 553 * @param rootAlias The root of trust alias. 554 * @return The value that were previously set, or null if there's none. 555 * 556 * @hide 557 */ 558 @Nullable getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias)559 public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) { 560 byte[] bytes = getBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH); 561 if (bytes == null) { 562 return null; 563 } 564 try { 565 return decodeCertPath(bytes); 566 } catch (CertificateException e) { 567 Log.wtf(TAG, 568 String.format(Locale.US, 569 "Recovery service CertPath entry cannot be decoded for " 570 + "userId=%d uid=%d.", 571 userId, uid), e); 572 return null; 573 } 574 } 575 576 /** 577 * Sets the {@code CertPath} of the recovery service. 578 * 579 * @param userId The userId of the profile the application is running under. 580 * @param uid The uid of the application who initializes the local recovery components. 581 * @param rootAlias The root of trust alias. 582 * @param certPath The certificate path of the recovery service. 583 * @return The primary key of the inserted row, or -1 if failed. 584 * @hide 585 */ setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias, CertPath certPath)586 public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias, 587 CertPath certPath) throws CertificateEncodingException { 588 if (certPath.getCertificates().size() == 0) { 589 throw new CertificateEncodingException("No certificate contained in the cert path."); 590 } 591 return setBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH, 592 certPath.getEncoded(CERT_PATH_ENCODING)); 593 } 594 595 /** 596 * Returns the list of recovery agents initialized for given {@code userId} 597 * @param userId The userId of the profile the application is running under. 598 * @return The list of recovery agents 599 * @hide 600 */ getRecoveryAgents(int userId)601 public @NonNull List<Integer> getRecoveryAgents(int userId) { 602 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 603 604 String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID }; 605 String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 606 String[] selectionArguments = { Integer.toString(userId) }; 607 608 try ( 609 Cursor cursor = db.query( 610 RecoveryServiceMetadataEntry.TABLE_NAME, 611 projection, 612 selection, 613 selectionArguments, 614 /*groupBy=*/ null, 615 /*having=*/ null, 616 /*orderBy=*/ null) 617 ) { 618 int count = cursor.getCount(); 619 ArrayList<Integer> result = new ArrayList<>(count); 620 while (cursor.moveToNext()) { 621 int uid = cursor.getInt( 622 cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID)); 623 result.add(uid); 624 } 625 return result; 626 } 627 } 628 629 /** 630 * Returns the public key of the recovery service. 631 * 632 * @param userId The userId of the profile the application is running under. 633 * @param uid The uid of the application who initializes the local recovery components. 634 * 635 * @hide 636 */ 637 @Nullable getRecoveryServicePublicKey(int userId, int uid)638 public PublicKey getRecoveryServicePublicKey(int userId, int uid) { 639 byte[] keyBytes = 640 getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY); 641 if (keyBytes == null) { 642 return null; 643 } 644 try { 645 return decodeX509Key(keyBytes); 646 } catch (InvalidKeySpecException e) { 647 Log.wtf(TAG, 648 String.format(Locale.US, 649 "Recovery service public key entry cannot be decoded for " 650 + "userId=%d uid=%d.", 651 userId, uid)); 652 return null; 653 } 654 } 655 656 /** 657 * Updates the list of user secret types used for end-to-end encryption. 658 * If no secret types are set, recovery snapshot will not be created. 659 * See {@code KeyChainProtectionParams} 660 * 661 * @param userId The userId of the profile the application is running under. 662 * @param uid The uid of the application. 663 * @param secretTypes list of secret types 664 * @return The primary key of the updated row, or -1 if failed. 665 * 666 * @hide 667 */ setRecoverySecretTypes(int userId, int uid, int[] secretTypes)668 public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) { 669 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 670 ContentValues values = new ContentValues(); 671 StringJoiner joiner = new StringJoiner(","); 672 Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i))); 673 String typesAsCsv = joiner.toString(); 674 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv); 675 String selection = 676 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 677 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 678 ensureRecoveryServiceMetadataEntryExists(userId, uid); 679 return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, 680 new String[] {String.valueOf(userId), String.valueOf(uid)}); 681 } 682 683 /** 684 * Returns the list of secret types used for end-to-end encryption. 685 * 686 * @param userId The userId of the profile the application is running under. 687 * @param uid The uid of the application who initialized the local recovery components. 688 * @return Secret types or empty array, if types were not set. 689 * 690 * @hide 691 */ getRecoverySecretTypes(int userId, int uid)692 public @NonNull int[] getRecoverySecretTypes(int userId, int uid) { 693 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 694 695 String[] projection = { 696 RecoveryServiceMetadataEntry._ID, 697 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 698 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 699 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES}; 700 String selection = 701 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 702 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 703 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 704 705 try ( 706 Cursor cursor = db.query( 707 RecoveryServiceMetadataEntry.TABLE_NAME, 708 projection, 709 selection, 710 selectionArguments, 711 /*groupBy=*/ null, 712 /*having=*/ null, 713 /*orderBy=*/ null) 714 ) { 715 int count = cursor.getCount(); 716 if (count == 0) { 717 return new int[]{}; 718 } 719 if (count > 1) { 720 Log.wtf(TAG, 721 String.format(Locale.US, 722 "%d deviceId entries found for userId=%d uid=%d. " 723 + "Should only ever be 0 or 1.", count, userId, uid)); 724 return new int[]{}; 725 } 726 cursor.moveToFirst(); 727 int idx = cursor.getColumnIndexOrThrow( 728 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES); 729 if (cursor.isNull(idx)) { 730 return new int[]{}; 731 } 732 String csv = cursor.getString(idx); 733 if (TextUtils.isEmpty(csv)) { 734 return new int[]{}; 735 } 736 String[] types = csv.split(","); 737 int[] result = new int[types.length]; 738 for (int i = 0; i < types.length; i++) { 739 try { 740 result[i] = Integer.parseInt(types[i]); 741 } catch (NumberFormatException e) { 742 Log.wtf(TAG, "String format error " + e); 743 } 744 } 745 return result; 746 } 747 } 748 749 /** 750 * Active root of trust for the recovery agent. 751 * 752 * @param userId The userId of the profile the application is running under. 753 * @param uid The uid of the application. 754 * @param rootAlias The root of trust alias. 755 * @return The primary key of the updated row, or -1 if failed. 756 * 757 * @hide 758 */ setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias)759 public long setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias) { 760 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 761 ContentValues values = new ContentValues(); 762 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, rootAlias); 763 String selection = 764 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 765 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 766 ensureRecoveryServiceMetadataEntryExists(userId, uid); 767 return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, 768 selection, new String[] {String.valueOf(userId), String.valueOf(uid)}); 769 } 770 771 /** 772 * Active root of trust for the recovery agent. 773 * 774 * @param userId The userId of the profile the application is running under. 775 * @param uid The uid of the application who initialized the local recovery components. 776 * @return Active root of trust alias of null if it was not set 777 * 778 * @hide 779 */ getActiveRootOfTrust(int userId, int uid)780 public @Nullable String getActiveRootOfTrust(int userId, int uid) { 781 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 782 783 String[] projection = { 784 RecoveryServiceMetadataEntry._ID, 785 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 786 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 787 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST}; 788 String selection = 789 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 790 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 791 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 792 793 try ( 794 Cursor cursor = db.query( 795 RecoveryServiceMetadataEntry.TABLE_NAME, 796 projection, 797 selection, 798 selectionArguments, 799 /*groupBy=*/ null, 800 /*having=*/ null, 801 /*orderBy=*/ null) 802 ) { 803 int count = cursor.getCount(); 804 if (count == 0) { 805 return null; 806 } 807 if (count > 1) { 808 Log.wtf(TAG, 809 String.format(Locale.US, 810 "%d deviceId entries found for userId=%d uid=%d. " 811 + "Should only ever be 0 or 1.", count, userId, uid)); 812 return null; 813 } 814 cursor.moveToFirst(); 815 int idx = cursor.getColumnIndexOrThrow( 816 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST); 817 if (cursor.isNull(idx)) { 818 return null; 819 } 820 String result = cursor.getString(idx); 821 if (TextUtils.isEmpty(result)) { 822 return null; 823 } 824 return result; 825 } 826 } 827 828 /** 829 * Updates the counterId 830 * 831 * @param userId The userId of the profile the application is running under. 832 * @param uid The uid of the application. 833 * @param counterId The counterId. 834 * @return The primary key of the inserted row, or -1 if failed. 835 * 836 * @hide 837 */ setCounterId(int userId, int uid, long counterId)838 public long setCounterId(int userId, int uid, long counterId) { 839 return setLong(userId, uid, 840 RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, counterId); 841 } 842 843 /** 844 * Returns the counter id. 845 * 846 * @param userId The userId of the profile the application is running under. 847 * @param uid The uid of the application who initialized the local recovery components. 848 * @return The counter id 849 * 850 * @hide 851 */ 852 @Nullable getCounterId(int userId, int uid)853 public Long getCounterId(int userId, int uid) { 854 return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID); 855 } 856 857 /** 858 * Updates the server parameters given by the application initializing the local recovery 859 * components. 860 * 861 * @param userId The userId of the profile the application is running under. 862 * @param uid The uid of the application. 863 * @param serverParams The server parameters. 864 * @return The primary key of the inserted row, or -1 if failed. 865 * 866 * @hide 867 */ setServerParams(int userId, int uid, byte[] serverParams)868 public long setServerParams(int userId, int uid, byte[] serverParams) { 869 return setBytes(userId, uid, 870 RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams); 871 } 872 873 /** 874 * Returns the server paramters that was previously set by the application who initialized the 875 * local recovery service components. 876 * 877 * @param userId The userId of the profile the application is running under. 878 * @param uid The uid of the application who initialized the local recovery components. 879 * @return The server parameters that were previously set, or null if there's none. 880 * 881 * @hide 882 */ 883 @Nullable getServerParams(int userId, int uid)884 public byte[] getServerParams(int userId, int uid) { 885 return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS); 886 } 887 888 /** 889 * Updates the snapshot version. 890 * 891 * @param userId The userId of the profile the application is running under. 892 * @param uid The uid of the application. 893 * @param snapshotVersion The snapshot version 894 * @return The primary key of the inserted row, or -1 if failed. 895 * 896 * @hide 897 */ setSnapshotVersion(int userId, int uid, long snapshotVersion)898 public long setSnapshotVersion(int userId, int uid, long snapshotVersion) { 899 return setLong(userId, uid, 900 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION, snapshotVersion); 901 } 902 903 /** 904 * Returns the snapshot version 905 * 906 * @param userId The userId of the profile the application is running under. 907 * @param uid The uid of the application who initialized the local recovery components. 908 * @return The server parameters that were previously set, or null if there's none. 909 * 910 * @hide 911 */ 912 @Nullable getSnapshotVersion(int userId, int uid)913 public Long getSnapshotVersion(int userId, int uid) { 914 return getLong(userId, uid, 915 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION); 916 } 917 918 /** 919 * Updates a flag indicating that a new snapshot should be created. 920 * It will be {@code false} until the first application key is added. 921 * After that, the flag will be set to true, if one of the following values is updated: 922 * <ul> 923 * <li> List of application keys 924 * <li> Server params. 925 * <li> Lock-screen secret. 926 * <li> Lock-screen secret type. 927 * <li> Trusted hardware certificate. 928 * </ul> 929 * 930 * @param userId The userId of the profile the application is running under. 931 * @param uid The uid of the application. 932 * @param pending Should create snapshot flag. 933 * @return The primary key of the inserted row, or -1 if failed. 934 * 935 * @hide 936 */ setShouldCreateSnapshot(int userId, int uid, boolean pending)937 public long setShouldCreateSnapshot(int userId, int uid, boolean pending) { 938 return setLong(userId, uid, 939 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT, pending ? 1 : 0); 940 } 941 942 /** 943 * Returns {@code true} if new snapshot should be created. 944 * Returns {@code false} if the flag was never set. 945 * 946 * @param userId The userId of the profile the application is running under. 947 * @param uid The uid of the application who initialized the local recovery components. 948 * @return should create snapshot flag 949 * 950 * @hide 951 */ getShouldCreateSnapshot(int userId, int uid)952 public boolean getShouldCreateSnapshot(int userId, int uid) { 953 Long res = getLong(userId, uid, 954 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT); 955 return res != null && res != 0L; 956 } 957 958 959 /** 960 * Returns given long value from the database. 961 * 962 * @param userId The userId of the profile the application is running under. 963 * @param uid The uid of the application who initialized the local recovery components. 964 * @param key from {@code RecoveryServiceMetadataEntry} 965 * @return The value that were previously set, or null if there's none. 966 * 967 * @hide 968 */ getLong(int userId, int uid, String key)969 private Long getLong(int userId, int uid, String key) { 970 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 971 972 String[] projection = { 973 RecoveryServiceMetadataEntry._ID, 974 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 975 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 976 key}; 977 String selection = 978 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 979 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 980 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 981 982 try ( 983 Cursor cursor = db.query( 984 RecoveryServiceMetadataEntry.TABLE_NAME, 985 projection, 986 selection, 987 selectionArguments, 988 /*groupBy=*/ null, 989 /*having=*/ null, 990 /*orderBy=*/ null) 991 ) { 992 int count = cursor.getCount(); 993 if (count == 0) { 994 return null; 995 } 996 if (count > 1) { 997 Log.wtf(TAG, 998 String.format(Locale.US, 999 "%d entries found for userId=%d uid=%d. " 1000 + "Should only ever be 0 or 1.", count, userId, uid)); 1001 return null; 1002 } 1003 cursor.moveToFirst(); 1004 int idx = cursor.getColumnIndexOrThrow(key); 1005 if (cursor.isNull(idx)) { 1006 return null; 1007 } else { 1008 return cursor.getLong(idx); 1009 } 1010 } 1011 } 1012 1013 /** 1014 * Sets a long value in the database. 1015 * 1016 * @param userId The userId of the profile the application is running under. 1017 * @param uid The uid of the application who initialized the local recovery components. 1018 * @param key defined in {@code RecoveryServiceMetadataEntry} 1019 * @param value new value. 1020 * @return The primary key of the inserted row, or -1 if failed. 1021 * 1022 * @hide 1023 */ 1024 setLong(int userId, int uid, String key, long value)1025 private long setLong(int userId, int uid, String key, long value) { 1026 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1027 ContentValues values = new ContentValues(); 1028 values.put(key, value); 1029 String selection = 1030 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 1031 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 1032 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 1033 1034 ensureRecoveryServiceMetadataEntryExists(userId, uid); 1035 return db.update( 1036 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); 1037 } 1038 1039 /** 1040 * Returns given binary value from the database. 1041 * 1042 * @param userId The userId of the profile the application is running under. 1043 * @param uid The uid of the application who initialized the local recovery components. 1044 * @param key from {@code RecoveryServiceMetadataEntry} 1045 * @return The value that were previously set, or null if there's none. 1046 * 1047 * @hide 1048 */ getBytes(int userId, int uid, String key)1049 private byte[] getBytes(int userId, int uid, String key) { 1050 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 1051 1052 String[] projection = { 1053 RecoveryServiceMetadataEntry._ID, 1054 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 1055 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 1056 key}; 1057 String selection = 1058 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 1059 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 1060 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 1061 1062 try ( 1063 Cursor cursor = db.query( 1064 RecoveryServiceMetadataEntry.TABLE_NAME, 1065 projection, 1066 selection, 1067 selectionArguments, 1068 /*groupBy=*/ null, 1069 /*having=*/ null, 1070 /*orderBy=*/ null) 1071 ) { 1072 int count = cursor.getCount(); 1073 if (count == 0) { 1074 return null; 1075 } 1076 if (count > 1) { 1077 Log.wtf(TAG, 1078 String.format(Locale.US, 1079 "%d entries found for userId=%d uid=%d. " 1080 + "Should only ever be 0 or 1.", count, userId, uid)); 1081 return null; 1082 } 1083 cursor.moveToFirst(); 1084 int idx = cursor.getColumnIndexOrThrow(key); 1085 if (cursor.isNull(idx)) { 1086 return null; 1087 } else { 1088 return cursor.getBlob(idx); 1089 } 1090 } 1091 } 1092 1093 /** 1094 * Sets a binary value in the database. 1095 * 1096 * @param userId The userId of the profile the application is running under. 1097 * @param uid The uid of the application who initialized the local recovery components. 1098 * @param key defined in {@code RecoveryServiceMetadataEntry} 1099 * @param value new value. 1100 * @return The primary key of the inserted row, or -1 if failed. 1101 * 1102 * @hide 1103 */ setBytes(int userId, int uid, String key, byte[] value)1104 private long setBytes(int userId, int uid, String key, byte[] value) { 1105 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1106 ContentValues values = new ContentValues(); 1107 values.put(key, value); 1108 String selection = 1109 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 1110 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 1111 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 1112 1113 ensureRecoveryServiceMetadataEntryExists(userId, uid); 1114 return db.update( 1115 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); 1116 } 1117 1118 /** 1119 * Returns given binary value from the database. 1120 * 1121 * @param userId The userId of the profile the application is running under. 1122 * @param uid The uid of the application who initialized the local recovery components. 1123 * @param rootAlias The root of trust alias. 1124 * @param key from {@code RootOfTrustEntry} 1125 * @return The value that were previously set, or null if there's none. 1126 * 1127 * @hide 1128 */ getBytes(int userId, int uid, String rootAlias, String key)1129 private byte[] getBytes(int userId, int uid, String rootAlias, String key) { 1130 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1131 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 1132 1133 String[] projection = { 1134 RootOfTrustEntry._ID, 1135 RootOfTrustEntry.COLUMN_NAME_USER_ID, 1136 RootOfTrustEntry.COLUMN_NAME_UID, 1137 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, 1138 key}; 1139 String selection = 1140 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1141 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1142 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1143 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1144 1145 try ( 1146 Cursor cursor = db.query( 1147 RootOfTrustEntry.TABLE_NAME, 1148 projection, 1149 selection, 1150 selectionArguments, 1151 /*groupBy=*/ null, 1152 /*having=*/ null, 1153 /*orderBy=*/ null) 1154 ) { 1155 int count = cursor.getCount(); 1156 if (count == 0) { 1157 return null; 1158 } 1159 if (count > 1) { 1160 Log.wtf(TAG, 1161 String.format(Locale.US, 1162 "%d entries found for userId=%d uid=%d. " 1163 + "Should only ever be 0 or 1.", count, userId, uid)); 1164 return null; 1165 } 1166 cursor.moveToFirst(); 1167 int idx = cursor.getColumnIndexOrThrow(key); 1168 if (cursor.isNull(idx)) { 1169 return null; 1170 } else { 1171 return cursor.getBlob(idx); 1172 } 1173 } 1174 } 1175 1176 /** 1177 * Sets a binary value in the database. 1178 * 1179 * @param userId The userId of the profile the application is running under. 1180 * @param uid The uid of the application who initialized the local recovery components. 1181 * @param rootAlias The root of trust alias. 1182 * @param key defined in {@code RootOfTrustEntry} 1183 * @param value new value. 1184 * @return The primary key of the inserted row, or -1 if failed. 1185 * 1186 * @hide 1187 */ setBytes(int userId, int uid, String rootAlias, String key, byte[] value)1188 private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) { 1189 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1190 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1191 ContentValues values = new ContentValues(); 1192 values.put(key, value); 1193 String selection = 1194 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1195 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1196 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1197 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1198 1199 ensureRootOfTrustEntryExists(userId, uid, rootAlias); 1200 return db.update( 1201 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments); 1202 } 1203 1204 /** 1205 * Returns given long value from the database. 1206 * 1207 * @param userId The userId of the profile the application is running under. 1208 * @param uid The uid of the application who initialized the local recovery components. 1209 * @param rootAlias The root of trust alias. 1210 * @param key from {@code RootOfTrustEntry} 1211 * @return The value that were previously set, or null if there's none. 1212 * 1213 * @hide 1214 */ getLong(int userId, int uid, String rootAlias, String key)1215 private Long getLong(int userId, int uid, String rootAlias, String key) { 1216 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1217 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 1218 1219 String[] projection = { 1220 RootOfTrustEntry._ID, 1221 RootOfTrustEntry.COLUMN_NAME_USER_ID, 1222 RootOfTrustEntry.COLUMN_NAME_UID, 1223 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, 1224 key}; 1225 String selection = 1226 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1227 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1228 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1229 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1230 1231 try ( 1232 Cursor cursor = db.query( 1233 RootOfTrustEntry.TABLE_NAME, 1234 projection, 1235 selection, 1236 selectionArguments, 1237 /*groupBy=*/ null, 1238 /*having=*/ null, 1239 /*orderBy=*/ null) 1240 ) { 1241 int count = cursor.getCount(); 1242 if (count == 0) { 1243 return null; 1244 } 1245 if (count > 1) { 1246 Log.wtf(TAG, 1247 String.format(Locale.US, 1248 "%d entries found for userId=%d uid=%d. " 1249 + "Should only ever be 0 or 1.", count, userId, uid)); 1250 return null; 1251 } 1252 cursor.moveToFirst(); 1253 int idx = cursor.getColumnIndexOrThrow(key); 1254 if (cursor.isNull(idx)) { 1255 return null; 1256 } else { 1257 return cursor.getLong(idx); 1258 } 1259 } 1260 } 1261 1262 /** 1263 * Sets a long value in the database. 1264 * 1265 * @param userId The userId of the profile the application is running under. 1266 * @param uid The uid of the application who initialized the local recovery components. 1267 * @param rootAlias The root of trust alias. 1268 * @param key defined in {@code RootOfTrustEntry} 1269 * @param value new value. 1270 * @return The primary key of the inserted row, or -1 if failed. 1271 * 1272 * @hide 1273 */ 1274 setLong(int userId, int uid, String rootAlias, String key, long value)1275 private long setLong(int userId, int uid, String rootAlias, String key, long value) { 1276 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1277 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1278 ContentValues values = new ContentValues(); 1279 values.put(key, value); 1280 String selection = 1281 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1282 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1283 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1284 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1285 1286 ensureRootOfTrustEntryExists(userId, uid, rootAlias); 1287 return db.update( 1288 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments); 1289 } 1290 1291 /** 1292 * Removes all entries for given {@code userId}. 1293 */ removeUserFromAllTables(int userId)1294 public void removeUserFromAllTables(int userId) { 1295 removeUserFromKeysTable(userId); 1296 removeUserFromUserMetadataTable(userId); 1297 removeUserFromRecoveryServiceMetadataTable(userId); 1298 removeUserFromRootOfTrustTable(userId); 1299 } 1300 1301 /** 1302 * Removes all entries for given userId from Keys table. 1303 * 1304 * @return {@code true} if deleted a row. 1305 */ removeUserFromKeysTable(int userId)1306 private boolean removeUserFromKeysTable(int userId) { 1307 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1308 String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?"; 1309 String[] selectionArgs = {Integer.toString(userId)}; 1310 return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0; 1311 } 1312 1313 /** 1314 * Removes all entries for given userId from UserMetadata table. 1315 * 1316 * @return {@code true} if deleted a row. 1317 */ removeUserFromUserMetadataTable(int userId)1318 private boolean removeUserFromUserMetadataTable(int userId) { 1319 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1320 String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 1321 String[] selectionArgs = {Integer.toString(userId)}; 1322 return db.delete(UserMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0; 1323 } 1324 1325 /** 1326 * Removes all entries for given userId from RecoveryServiceMetadata table. 1327 * 1328 * @return {@code true} if deleted a row. 1329 */ removeUserFromRecoveryServiceMetadataTable(int userId)1330 private boolean removeUserFromRecoveryServiceMetadataTable(int userId) { 1331 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1332 String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 1333 String[] selectionArgs = {Integer.toString(userId)}; 1334 return db.delete(RecoveryServiceMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0; 1335 } 1336 1337 /** 1338 * Removes all entries for given userId from RootOfTrust table. 1339 * 1340 * @return {@code true} if deleted a row. 1341 */ removeUserFromRootOfTrustTable(int userId)1342 private boolean removeUserFromRootOfTrustTable(int userId) { 1343 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1344 String selection = RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ?"; 1345 String[] selectionArgs = {Integer.toString(userId)}; 1346 return db.delete(RootOfTrustEntry.TABLE_NAME, selection, selectionArgs) > 0; 1347 } 1348 1349 /** 1350 * Creates an empty row in the recovery service metadata table if such a row doesn't exist for 1351 * the given userId and uid, so db.update will succeed. 1352 */ ensureRecoveryServiceMetadataEntryExists(int userId, int uid)1353 private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) { 1354 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1355 ContentValues values = new ContentValues(); 1356 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId); 1357 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid); 1358 db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, 1359 values, SQLiteDatabase.CONFLICT_IGNORE); 1360 } 1361 1362 /** 1363 * Creates an empty row in the root of trust table if such a row doesn't exist for 1364 * the given userId and uid, so db.update will succeed. 1365 */ ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias)1366 private void ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias) { 1367 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1368 ContentValues values = new ContentValues(); 1369 values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, userId); 1370 values.put(RootOfTrustEntry.COLUMN_NAME_UID, uid); 1371 values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, rootAlias); 1372 db.insertWithOnConflict(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null, 1373 values, SQLiteDatabase.CONFLICT_IGNORE); 1374 } 1375 1376 /** 1377 * Creates an empty row in the user metadata table if such a row doesn't exist for 1378 * the given userId, so db.update will succeed. 1379 */ ensureUserMetadataEntryExists(int userId)1380 private void ensureUserMetadataEntryExists(int userId) { 1381 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1382 ContentValues values = new ContentValues(); 1383 values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); 1384 db.insertWithOnConflict(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, 1385 values, SQLiteDatabase.CONFLICT_IGNORE); 1386 } 1387 1388 /** 1389 * Closes all open connections to the database. 1390 */ close()1391 public void close() { 1392 mKeyStoreDbHelper.close(); 1393 } 1394 1395 @Nullable decodeX509Key(byte[] keyBytes)1396 private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException { 1397 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes); 1398 try { 1399 return KeyFactory.getInstance("EC").generatePublic(publicKeySpec); 1400 } catch (NoSuchAlgorithmException e) { 1401 // Should never happen 1402 throw new RuntimeException(e); 1403 } 1404 } 1405 1406 @Nullable decodeCertPath(byte[] bytes)1407 private static CertPath decodeCertPath(byte[] bytes) throws CertificateException { 1408 CertificateFactory certFactory; 1409 try { 1410 certFactory = CertificateFactory.getInstance("X.509"); 1411 } catch (CertificateException e) { 1412 // Should not happen, as X.509 is mandatory for all providers. 1413 throw new RuntimeException(e); 1414 } 1415 return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING); 1416 } 1417 } 1418