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 /** 396 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 397 */ invalidateKeysForUser(int userId)398 public void invalidateKeysForUser(int userId) { 399 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 400 ContentValues values = new ContentValues(); 401 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 402 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); 403 String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?"; 404 db.update(KeysEntry.TABLE_NAME, values, selection, new String[] {String.valueOf(userId)}); 405 } 406 407 /** 408 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 409 */ invalidateKeysForUserIdOnCustomScreenLock(int userId)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 /** 422 * Returns the generation ID associated with the platform key of the user with {@code userId}. 423 */ getPlatformKeyGenerationId(int userId)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 /** 454 * Updates the public key of the recovery service into the database. 455 * 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 */ setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey)463 public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) { 464 return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, 465 publicKey.getEncoded()); 466 } 467 468 /** 469 * 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. 473 * @param rootAlias The root of trust alias. 474 * @return The value that were previously set, or null if there's none. 475 * 476 * @hide 477 */ 478 @Nullable getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias)479 public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) { 480 return getLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL); 481 } 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. 488 * @param rootAlias The root of trust alias. 489 * @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 */ setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias, long serial)494 public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias, 495 long serial) { 496 return setLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL, 497 serial); 498 } 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. 505 * @param rootAlias The root of trust alias. 506 * @return The value that were previously set, or null if there's none. 507 * 508 * @hide 509 */ 510 @Nullable getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias)511 public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) { 512 byte[] bytes = getBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH); 513 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. 533 * @param rootAlias The root of trust alias. 534 * @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 */ setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias, CertPath certPath)538 public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias, 539 CertPath certPath) throws CertificateEncodingException { 540 if (certPath.getCertificates().size() == 0) { 541 throw new CertificateEncodingException("No certificate contained in the cert path."); 542 } 543 return setBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH, 544 certPath.getEncoded(CERT_PATH_ENCODING)); 545 } 546 547 /** 548 * 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 552 */ getRecoveryAgents(int userId)553 public @NonNull List<Integer> getRecoveryAgents(int userId) { 554 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(); 571 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); 576 } 577 return result; 578 } 579 } 580 581 /** 582 * Returns the public key of the recovery service. 583 * 584 * @param userId The userId of the profile the application is running under. 585 * @param uid The uid of the application who initializes the local recovery components. 586 * 587 * @hide 588 */ 589 @Nullable getRecoveryServicePublicKey(int userId, int uid)590 public PublicKey getRecoveryServicePublicKey(int userId, int uid) { 591 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; 605 } 606 } 607 608 /** 609 * 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. 611 * See {@code KeyChainProtectionParams} 612 * 613 * @param userId The userId of the profile the application is running under. 614 * @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 */ setRecoverySecretTypes(int userId, int uid, int[] secretTypes)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 * 638 * @param userId The userId of the profile the application is running under. 639 * @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 */ getRecoverySecretTypes(int userId, int uid)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(","); 689 int[] result = new int[types.length]; 690 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 /** 702 * 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 */ setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias)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 */ getActiveRootOfTrust(int userId, int uid)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 /** 781 * 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 */ setCounterId(int userId, int uid, long counterId)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 getCounterId(int userId, int uid)805 public Long getCounterId(int userId, int uid) { 806 return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID); 807 } 808 809 /** 810 * Updates the server parameters given by the application initializing the local recovery 811 * components. 812 * 813 * @param userId The userId of the profile the application is running under. 814 * @param uid The uid of the application. 815 * @param serverParams The server parameters. 816 * @return The primary key of the inserted row, or -1 if failed. 817 * 818 * @hide 819 */ setServerParams(int userId, int uid, byte[] serverParams)820 public long setServerParams(int userId, int uid, byte[] serverParams) { 821 return setBytes(userId, uid, 822 RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams); 823 } 824 825 /** 826 * Returns the server paramters that was previously set by the application who initialized the 827 * local recovery service components. 828 * 829 * @param userId The userId of the profile the application is running under. 830 * @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 */ 835 @Nullable getServerParams(int userId, int uid)836 public byte[] getServerParams(int userId, int uid) { 837 return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS); 838 } 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 */ setSnapshotVersion(int userId, int uid, long snapshotVersion)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 getSnapshotVersion(int userId, int uid)865 public Long getSnapshotVersion(int userId, int uid) { 866 return getLong(userId, uid, 867 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION); 868 } 869 870 /** 871 * 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> 881 * 882 * @param userId The userId of the profile the application is running under. 883 * @param uid The uid of the application. 884 * @param pending Should create snapshot flag. 885 * @return The primary key of the inserted row, or -1 if failed. 886 * 887 * @hide 888 */ setShouldCreateSnapshot(int userId, int uid, boolean pending)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. 900 * @return should create snapshot flag 901 * 902 * @hide 903 */ getShouldCreateSnapshot(int userId, int uid)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 910 911 /** 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 */ getLong(int userId, int uid, String key)921 private Long getLong(int userId, int uid, String key) { 922 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 923 924 String[] projection = { 925 RecoveryServiceMetadataEntry._ID, 926 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 927 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 928 key}; 929 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 ( 935 Cursor cursor = db.query( 936 RecoveryServiceMetadataEntry.TABLE_NAME, 937 projection, 938 selection, 939 selectionArguments, 940 /*groupBy=*/ null, 941 /*having=*/ null, 942 /*orderBy=*/ null) 943 ) { 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, 951 "%d entries found for userId=%d uid=%d. " 952 + "Should only ever be 0 or 1.", count, userId, uid)); 953 return null; 954 } 955 cursor.moveToFirst(); 956 int idx = cursor.getColumnIndexOrThrow(key); 957 if (cursor.isNull(idx)) { 958 return null; 959 } else { 960 return cursor.getLong(idx); 961 } 962 } 963 } 964 965 /** 966 * 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 setLong(int userId, int uid, String key, long value)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 /** 992 * 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 */ getBytes(int userId, int uid, String key)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 */ setBytes(int userId, int uid, String key, byte[] value)1056 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 /** 1071 * 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 */ getBytes(int userId, int uid, String rootAlias, String key)1081 private byte[] getBytes(int userId, int uid, String rootAlias, String key) { 1082 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1083 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 */ setBytes(int userId, int uid, String rootAlias, String key, byte[] value)1140 private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) { 1141 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1142 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 */ getLong(int userId, int uid, String rootAlias, String key)1167 private Long getLong(int userId, int uid, String rootAlias, String key) { 1168 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1169 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 setLong(int userId, int uid, String rootAlias, String key, long value)1227 private long setLong(int userId, int uid, String rootAlias, String key, long value) { 1228 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1229 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 1243 /** 1244 * Removes all entries for given {@code userId}. 1245 */ removeUserFromAllTables(int userId)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 */ removeUserFromKeysTable(int userId)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 */ removeUserFromUserMetadataTable(int userId)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 */ removeUserFromRecoveryServiceMetadataTable(int userId)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 */ removeUserFromRootOfTrustTable(int userId)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 } 1300 1301 /** 1302 * 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 */ ensureRecoveryServiceMetadataEntryExists(int userId, int uid)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 /** 1315 * 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 */ ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias)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 /** 1329 * Creates an empty row in the user metadata table if such a row doesn't exist for 1330 * the given userId, so db.update will succeed. 1331 */ ensureUserMetadataEntryExists(int userId)1332 private void ensureUserMetadataEntryExists(int userId) { 1333 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1334 ContentValues values = new ContentValues(); 1335 values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); 1336 db.insertWithOnConflict(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, 1337 values, SQLiteDatabase.CONFLICT_IGNORE); 1338 } 1339 1340 /** 1341 * Closes all open connections to the database. 1342 */ close()1343 public void close() { 1344 mKeyStoreDbHelper.close(); 1345 } 1346 1347 @Nullable decodeX509Key(byte[] keyBytes)1348 private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException { 1349 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes); 1350 try { 1351 return KeyFactory.getInstance("EC").generatePublic(publicKeySpec); 1352 } catch (NoSuchAlgorithmException e) { 1353 // Should never happen 1354 throw new RuntimeException(e); 1355 } 1356 } 1357 1358 @Nullable decodeCertPath(byte[] bytes)1359 private static CertPath decodeCertPath(byte[] bytes) throws CertificateException { 1360 CertificateFactory certFactory; 1361 try { 1362 certFactory = CertificateFactory.getInstance("X.509"); 1363 } catch (CertificateException e) { 1364 // Should not happen, as X.509 is mandatory for all providers. 1365 throw new RuntimeException(e); 1366 } 1367 return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING); 1368 } 1369 } 1370