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.Log; 28 29 import com.android.server.locksettings.recoverablekeystore.TestOnlyInsecureCertificateHelper; 30 import com.android.server.locksettings.recoverablekeystore.WrappedKey; 31 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; 32 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry; 33 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry; 34 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; 35 36 import java.io.ByteArrayInputStream; 37 import java.security.KeyFactory; 38 import java.security.NoSuchAlgorithmException; 39 import java.security.PublicKey; 40 import java.security.cert.CertPath; 41 import java.security.cert.CertificateEncodingException; 42 import java.security.cert.CertificateException; 43 import java.security.cert.CertificateFactory; 44 import java.security.spec.InvalidKeySpecException; 45 import java.security.spec.X509EncodedKeySpec; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Locale; 51 import java.util.Map; 52 import java.util.StringJoiner; 53 54 /** 55 * Database of recoverable key information. 56 * 57 * @hide 58 */ 59 public class RecoverableKeyStoreDb { 60 private static final String TAG = "RecoverableKeyStoreDb"; 61 private static final int IDLE_TIMEOUT_SECONDS = 30; 62 private static final int LAST_SYNCED_AT_UNSYNCED = -1; 63 private static final String CERT_PATH_ENCODING = "PkiPath"; 64 65 private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper; 66 private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; 67 68 /** 69 * A new instance, storing the database in the user directory of {@code context}. 70 * 71 * @hide 72 */ 73 public static RecoverableKeyStoreDb newInstance(Context context) { 74 RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context); 75 helper.setWriteAheadLoggingEnabled(true); 76 helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS); 77 return new RecoverableKeyStoreDb(helper); 78 } 79 80 private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { 81 this.mKeyStoreDbHelper = keyStoreDbHelper; 82 this.mTestOnlyInsecureCertificateHelper = new TestOnlyInsecureCertificateHelper(); 83 } 84 85 /** 86 * Inserts a key into the database. 87 * 88 * @param userId The uid of the profile the application is running under. 89 * @param uid Uid of the application to whom the key belongs. 90 * @param alias The alias of the key in the AndroidKeyStore. 91 * @param wrappedKey The wrapped key. 92 * @return The primary key of the inserted row, or -1 if failed. 93 * 94 * @hide 95 */ 96 public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) { 97 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 98 ContentValues values = new ContentValues(); 99 values.put(KeysEntry.COLUMN_NAME_USER_ID, userId); 100 values.put(KeysEntry.COLUMN_NAME_UID, uid); 101 values.put(KeysEntry.COLUMN_NAME_ALIAS, alias); 102 values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce()); 103 values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial()); 104 values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED); 105 values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); 106 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus()); 107 return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); 108 } 109 110 /** 111 * Gets the key with {@code alias} for the app with {@code uid}. 112 * 113 * @hide 114 */ 115 @Nullable public WrappedKey getKey(int uid, String alias) { 116 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 117 String[] projection = { 118 KeysEntry._ID, 119 KeysEntry.COLUMN_NAME_NONCE, 120 KeysEntry.COLUMN_NAME_WRAPPED_KEY, 121 KeysEntry.COLUMN_NAME_GENERATION_ID, 122 KeysEntry.COLUMN_NAME_RECOVERY_STATUS}; 123 String selection = 124 KeysEntry.COLUMN_NAME_UID + " = ? AND " 125 + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; 126 String[] selectionArguments = { Integer.toString(uid), alias }; 127 128 try ( 129 Cursor cursor = db.query( 130 KeysEntry.TABLE_NAME, 131 projection, 132 selection, 133 selectionArguments, 134 /*groupBy=*/ null, 135 /*having=*/ null, 136 /*orderBy=*/ null) 137 ) { 138 int count = cursor.getCount(); 139 if (count == 0) { 140 return null; 141 } 142 if (count > 1) { 143 Log.wtf(TAG, 144 String.format(Locale.US, 145 "%d WrappedKey entries found for uid=%d alias='%s'. " 146 + "Should only ever be 0 or 1.", count, uid, alias)); 147 return null; 148 } 149 cursor.moveToFirst(); 150 byte[] nonce = cursor.getBlob( 151 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); 152 byte[] keyMaterial = cursor.getBlob( 153 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); 154 int generationId = cursor.getInt( 155 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID)); 156 int recoveryStatus = cursor.getInt( 157 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); 158 return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus); 159 } 160 } 161 162 /** 163 * Removes key with {@code alias} for app with {@code uid}. 164 * 165 * @return {@code true} if deleted a row. 166 */ 167 public boolean removeKey(int uid, String alias) { 168 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 169 String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " + 170 KeysEntry.COLUMN_NAME_ALIAS + " = ?"; 171 String[] selectionArgs = { Integer.toString(uid), alias }; 172 return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0; 173 } 174 175 /** 176 * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}. 177 * 178 * @param uid of the application 179 * 180 * @return Map from Aliases to status. 181 * 182 * @hide 183 */ 184 public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) { 185 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 186 String[] projection = { 187 KeysEntry._ID, 188 KeysEntry.COLUMN_NAME_ALIAS, 189 KeysEntry.COLUMN_NAME_RECOVERY_STATUS}; 190 String selection = 191 KeysEntry.COLUMN_NAME_UID + " = ?"; 192 String[] selectionArguments = {Integer.toString(uid)}; 193 194 try ( 195 Cursor cursor = db.query( 196 KeysEntry.TABLE_NAME, 197 projection, 198 selection, 199 selectionArguments, 200 /*groupBy=*/ null, 201 /*having=*/ null, 202 /*orderBy=*/ null) 203 ) { 204 HashMap<String, Integer> statuses = new HashMap<>(); 205 while (cursor.moveToNext()) { 206 String alias = cursor.getString( 207 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); 208 int recoveryStatus = cursor.getInt( 209 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); 210 statuses.put(alias, recoveryStatus); 211 } 212 return statuses; 213 } 214 } 215 216 /** 217 * Updates status for given key. 218 * @param uid of the application 219 * @param alias of the key 220 * @param status - new status 221 * @return number of updated entries. 222 * @hide 223 **/ 224 public int setRecoveryStatus(int uid, String alias, int status) { 225 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 226 ContentValues values = new ContentValues(); 227 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status); 228 String selection = 229 KeysEntry.COLUMN_NAME_UID + " = ? AND " 230 + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; 231 return db.update(KeysEntry.TABLE_NAME, values, selection, 232 new String[] {String.valueOf(uid), alias}); 233 } 234 235 /** 236 * Returns all keys for the given {@code userId} {@code recoveryAgentUid} 237 * and {@code platformKeyGenerationId}. 238 * 239 * @param userId User id of the profile to which all the keys are associated. 240 * @param recoveryAgentUid Uid of the recovery agent which will perform the sync 241 * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys. 242 * (i.e., this should be the most recent generation ID, as older platform keys are not 243 * usable.) 244 * 245 * @hide 246 */ 247 public Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid, 248 int platformKeyGenerationId) { 249 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 250 String[] projection = { 251 KeysEntry._ID, 252 KeysEntry.COLUMN_NAME_NONCE, 253 KeysEntry.COLUMN_NAME_WRAPPED_KEY, 254 KeysEntry.COLUMN_NAME_ALIAS, 255 KeysEntry.COLUMN_NAME_RECOVERY_STATUS}; 256 String selection = 257 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND " 258 + KeysEntry.COLUMN_NAME_UID + " = ? AND " 259 + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; 260 String[] selectionArguments = { 261 Integer.toString(userId), 262 Integer.toString(recoveryAgentUid), 263 Integer.toString(platformKeyGenerationId) 264 }; 265 266 try ( 267 Cursor cursor = db.query( 268 KeysEntry.TABLE_NAME, 269 projection, 270 selection, 271 selectionArguments, 272 /*groupBy=*/ null, 273 /*having=*/ null, 274 /*orderBy=*/ null) 275 ) { 276 HashMap<String, WrappedKey> keys = new HashMap<>(); 277 while (cursor.moveToNext()) { 278 byte[] nonce = cursor.getBlob( 279 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); 280 byte[] keyMaterial = cursor.getBlob( 281 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); 282 String alias = cursor.getString( 283 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); 284 int recoveryStatus = cursor.getInt( 285 cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); 286 keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId, 287 recoveryStatus)); 288 } 289 return keys; 290 } 291 } 292 293 /** 294 * Sets the {@code generationId} of the platform key for user {@code userId}. 295 * 296 * @return The primary key ID of the relation. 297 */ 298 public long setPlatformKeyGenerationId(int userId, int generationId) { 299 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 300 ContentValues values = new ContentValues(); 301 values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); 302 values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId); 303 long result = db.replace( 304 UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); 305 if (result != -1) { 306 invalidateKeysWithOldGenerationId(userId, generationId); 307 } 308 return result; 309 } 310 311 /** 312 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 313 */ 314 public void invalidateKeysWithOldGenerationId(int userId, int newGenerationId) { 315 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 316 ContentValues values = new ContentValues(); 317 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 318 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); 319 String selection = 320 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND " 321 + KeysEntry.COLUMN_NAME_GENERATION_ID + " < ?"; 322 db.update(KeysEntry.TABLE_NAME, values, selection, 323 new String[] {String.valueOf(userId), String.valueOf(newGenerationId)}); 324 } 325 326 /** 327 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 328 */ 329 public void invalidateKeysForUserIdOnCustomScreenLock(int userId) { 330 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 331 ContentValues values = new ContentValues(); 332 values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, 333 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); 334 String selection = 335 KeysEntry.COLUMN_NAME_USER_ID + " = ?"; 336 db.update(KeysEntry.TABLE_NAME, values, selection, 337 new String[] {String.valueOf(userId)}); 338 } 339 340 /** 341 * Returns the generation ID associated with the platform key of the user with {@code userId}. 342 */ 343 public int getPlatformKeyGenerationId(int userId) { 344 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 345 String[] projection = { 346 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID}; 347 String selection = 348 UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 349 String[] selectionArguments = { 350 Integer.toString(userId)}; 351 352 try ( 353 Cursor cursor = db.query( 354 UserMetadataEntry.TABLE_NAME, 355 projection, 356 selection, 357 selectionArguments, 358 /*groupBy=*/ null, 359 /*having=*/ null, 360 /*orderBy=*/ null) 361 ) { 362 if (cursor.getCount() == 0) { 363 return -1; 364 } 365 cursor.moveToFirst(); 366 return cursor.getInt( 367 cursor.getColumnIndexOrThrow( 368 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID)); 369 } 370 } 371 372 /** 373 * Updates the public key of the recovery service into the database. 374 * 375 * @param userId The uid of the profile the application is running under. 376 * @param uid The uid of the application to whom the key belongs. 377 * @param publicKey The public key of the recovery service. 378 * @return The primary key of the inserted row, or -1 if failed. 379 * 380 * @hide 381 */ 382 public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) { 383 return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, 384 publicKey.getEncoded()); 385 } 386 387 /** 388 * Returns the serial number of the XML file containing certificates of the recovery service. 389 * 390 * @param userId The userId of the profile the application is running under. 391 * @param uid The uid of the application who initializes the local recovery components. 392 * @param rootAlias The root of trust alias. 393 * @return The value that were previously set, or null if there's none. 394 * 395 * @hide 396 */ 397 @Nullable 398 public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) { 399 return getLong(userId, uid, rootAlias, 400 RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL); 401 } 402 403 /** 404 * Records the serial number of the XML file containing certificates of the recovery service. 405 * 406 * @param userId The userId of the profile the application is running under. 407 * @param uid The uid of the application who initializes the local recovery components. 408 * @param rootAlias The root of trust alias. 409 * @param serial The serial number contained in the XML file for recovery service certificates. 410 * @return The primary key of the inserted row, or -1 if failed. 411 * 412 * @hide 413 */ 414 public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias, 415 long serial) { 416 return setLong(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, 417 serial); 418 } 419 420 /** 421 * Returns the {@code CertPath} of the recovery service. 422 * 423 * @param userId The userId of the profile the application is running under. 424 * @param uid The uid of the application who initializes the local recovery components. 425 * @param rootAlias The root of trust alias. 426 * @return The value that were previously set, or null if there's none. 427 * 428 * @hide 429 */ 430 @Nullable 431 public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) { 432 byte[] bytes = getBytes(userId, uid, rootAlias, 433 RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH); 434 if (bytes == null) { 435 return null; 436 } 437 try { 438 return decodeCertPath(bytes); 439 } catch (CertificateException e) { 440 Log.wtf(TAG, 441 String.format(Locale.US, 442 "Recovery service CertPath entry cannot be decoded for " 443 + "userId=%d uid=%d.", 444 userId, uid), e); 445 return null; 446 } 447 } 448 449 /** 450 * Sets the {@code CertPath} of the recovery service. 451 * 452 * @param userId The userId of the profile the application is running under. 453 * @param uid The uid of the application who initializes the local recovery components. 454 * @param rootAlias The root of trust alias. 455 * @param certPath The certificate path of the recovery service. 456 * @return The primary key of the inserted row, or -1 if failed. 457 * @hide 458 */ 459 public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias, 460 CertPath certPath) throws CertificateEncodingException { 461 if (certPath.getCertificates().size() == 0) { 462 throw new CertificateEncodingException("No certificate contained in the cert path."); 463 } 464 return setBytes(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, 465 certPath.getEncoded(CERT_PATH_ENCODING)); 466 } 467 468 /** 469 * Returns the list of recovery agents initialized for given {@code userId} 470 * @param userId The userId of the profile the application is running under. 471 * @return The list of recovery agents 472 * @hide 473 */ 474 public @NonNull List<Integer> getRecoveryAgents(int userId) { 475 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 476 477 String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID }; 478 String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; 479 String[] selectionArguments = { Integer.toString(userId) }; 480 481 try ( 482 Cursor cursor = db.query( 483 RecoveryServiceMetadataEntry.TABLE_NAME, 484 projection, 485 selection, 486 selectionArguments, 487 /*groupBy=*/ null, 488 /*having=*/ null, 489 /*orderBy=*/ null) 490 ) { 491 int count = cursor.getCount(); 492 ArrayList<Integer> result = new ArrayList<>(count); 493 while (cursor.moveToNext()) { 494 int uid = cursor.getInt( 495 cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID)); 496 result.add(uid); 497 } 498 return result; 499 } 500 } 501 502 /** 503 * Returns the public key of the recovery service. 504 * 505 * @param userId The userId of the profile the application is running under. 506 * @param uid The uid of the application who initializes the local recovery components. 507 * 508 * @hide 509 */ 510 @Nullable 511 public PublicKey getRecoveryServicePublicKey(int userId, int uid) { 512 byte[] keyBytes = 513 getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY); 514 if (keyBytes == null) { 515 return null; 516 } 517 try { 518 return decodeX509Key(keyBytes); 519 } catch (InvalidKeySpecException e) { 520 Log.wtf(TAG, 521 String.format(Locale.US, 522 "Recovery service public key entry cannot be decoded for " 523 + "userId=%d uid=%d.", 524 userId, uid)); 525 return null; 526 } 527 } 528 529 /** 530 * Updates the list of user secret types used for end-to-end encryption. 531 * If no secret types are set, recovery snapshot will not be created. 532 * See {@code KeyChainProtectionParams} 533 * 534 * @param userId The userId of the profile the application is running under. 535 * @param uid The uid of the application. 536 * @param secretTypes list of secret types 537 * @return The primary key of the updated row, or -1 if failed. 538 * 539 * @hide 540 */ 541 public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) { 542 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 543 ContentValues values = new ContentValues(); 544 StringJoiner joiner = new StringJoiner(","); 545 Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i))); 546 String typesAsCsv = joiner.toString(); 547 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv); 548 String selection = 549 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 550 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 551 ensureRecoveryServiceMetadataEntryExists(userId, uid); 552 return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, 553 new String[] {String.valueOf(userId), String.valueOf(uid)}); 554 } 555 556 /** 557 * Returns the list of secret types used for end-to-end encryption. 558 * 559 * @param userId The userId of the profile the application is running under. 560 * @param uid The uid of the application who initialized the local recovery components. 561 * @return Secret types or empty array, if types were not set. 562 * 563 * @hide 564 */ 565 public @NonNull int[] getRecoverySecretTypes(int userId, int uid) { 566 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 567 568 String[] projection = { 569 RecoveryServiceMetadataEntry._ID, 570 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 571 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 572 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES}; 573 String selection = 574 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 575 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 576 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 577 578 try ( 579 Cursor cursor = db.query( 580 RecoveryServiceMetadataEntry.TABLE_NAME, 581 projection, 582 selection, 583 selectionArguments, 584 /*groupBy=*/ null, 585 /*having=*/ null, 586 /*orderBy=*/ null) 587 ) { 588 int count = cursor.getCount(); 589 if (count == 0) { 590 return new int[]{}; 591 } 592 if (count > 1) { 593 Log.wtf(TAG, 594 String.format(Locale.US, 595 "%d deviceId entries found for userId=%d uid=%d. " 596 + "Should only ever be 0 or 1.", count, userId, uid)); 597 return new int[]{}; 598 } 599 cursor.moveToFirst(); 600 int idx = cursor.getColumnIndexOrThrow( 601 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES); 602 if (cursor.isNull(idx)) { 603 return new int[]{}; 604 } 605 String csv = cursor.getString(idx); 606 if (TextUtils.isEmpty(csv)) { 607 return new int[]{}; 608 } 609 String[] types = csv.split(","); 610 int[] result = new int[types.length]; 611 for (int i = 0; i < types.length; i++) { 612 try { 613 result[i] = Integer.parseInt(types[i]); 614 } catch (NumberFormatException e) { 615 Log.wtf(TAG, "String format error " + e); 616 } 617 } 618 return result; 619 } 620 } 621 622 /** 623 * Active root of trust for the recovery agent. 624 * 625 * @param userId The userId of the profile the application is running under. 626 * @param uid The uid of the application. 627 * @param rootAlias The root of trust alias. 628 * @return The primary key of the updated row, or -1 if failed. 629 * 630 * @hide 631 */ 632 public long setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias) { 633 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 634 ContentValues values = new ContentValues(); 635 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, rootAlias); 636 String selection = 637 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 638 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 639 ensureRecoveryServiceMetadataEntryExists(userId, uid); 640 return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, 641 selection, new String[] {String.valueOf(userId), String.valueOf(uid)}); 642 } 643 644 /** 645 * Active root of trust for the recovery agent. 646 * 647 * @param userId The userId of the profile the application is running under. 648 * @param uid The uid of the application who initialized the local recovery components. 649 * @return Active root of trust alias of null if it was not set 650 * 651 * @hide 652 */ 653 public @Nullable String getActiveRootOfTrust(int userId, int uid) { 654 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 655 656 String[] projection = { 657 RecoveryServiceMetadataEntry._ID, 658 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 659 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 660 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST}; 661 String selection = 662 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 663 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 664 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 665 666 try ( 667 Cursor cursor = db.query( 668 RecoveryServiceMetadataEntry.TABLE_NAME, 669 projection, 670 selection, 671 selectionArguments, 672 /*groupBy=*/ null, 673 /*having=*/ null, 674 /*orderBy=*/ null) 675 ) { 676 int count = cursor.getCount(); 677 if (count == 0) { 678 return null; 679 } 680 if (count > 1) { 681 Log.wtf(TAG, 682 String.format(Locale.US, 683 "%d deviceId entries found for userId=%d uid=%d. " 684 + "Should only ever be 0 or 1.", count, userId, uid)); 685 return null; 686 } 687 cursor.moveToFirst(); 688 int idx = cursor.getColumnIndexOrThrow( 689 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST); 690 if (cursor.isNull(idx)) { 691 return null; 692 } 693 String result = cursor.getString(idx); 694 if (TextUtils.isEmpty(result)) { 695 return null; 696 } 697 return result; 698 } 699 } 700 701 /** 702 * Updates the counterId 703 * 704 * @param userId The userId of the profile the application is running under. 705 * @param uid The uid of the application. 706 * @param counterId The counterId. 707 * @return The primary key of the inserted row, or -1 if failed. 708 * 709 * @hide 710 */ 711 public long setCounterId(int userId, int uid, long counterId) { 712 return setLong(userId, uid, 713 RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, counterId); 714 } 715 716 /** 717 * Returns the counter id. 718 * 719 * @param userId The userId of the profile the application is running under. 720 * @param uid The uid of the application who initialized the local recovery components. 721 * @return The counter id 722 * 723 * @hide 724 */ 725 @Nullable 726 public Long getCounterId(int userId, int uid) { 727 return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID); 728 } 729 730 /** 731 * Updates the server parameters given by the application initializing the local recovery 732 * components. 733 * 734 * @param userId The userId of the profile the application is running under. 735 * @param uid The uid of the application. 736 * @param serverParams The server parameters. 737 * @return The primary key of the inserted row, or -1 if failed. 738 * 739 * @hide 740 */ 741 public long setServerParams(int userId, int uid, byte[] serverParams) { 742 return setBytes(userId, uid, 743 RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams); 744 } 745 746 /** 747 * Returns the server paramters that was previously set by the application who initialized the 748 * local recovery service components. 749 * 750 * @param userId The userId of the profile the application is running under. 751 * @param uid The uid of the application who initialized the local recovery components. 752 * @return The server parameters that were previously set, or null if there's none. 753 * 754 * @hide 755 */ 756 @Nullable 757 public byte[] getServerParams(int userId, int uid) { 758 return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS); 759 } 760 761 /** 762 * Updates the snapshot version. 763 * 764 * @param userId The userId of the profile the application is running under. 765 * @param uid The uid of the application. 766 * @param snapshotVersion The snapshot version 767 * @return The primary key of the inserted row, or -1 if failed. 768 * 769 * @hide 770 */ 771 public long setSnapshotVersion(int userId, int uid, long snapshotVersion) { 772 return setLong(userId, uid, 773 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION, snapshotVersion); 774 } 775 776 /** 777 * Returns the snapshot version 778 * 779 * @param userId The userId of the profile the application is running under. 780 * @param uid The uid of the application who initialized the local recovery components. 781 * @return The server parameters that were previously set, or null if there's none. 782 * 783 * @hide 784 */ 785 @Nullable 786 public Long getSnapshotVersion(int userId, int uid) { 787 return getLong(userId, uid, 788 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION); 789 } 790 791 /** 792 * Updates a flag indicating that a new snapshot should be created. 793 * It will be {@code false} until the first application key is added. 794 * After that, the flag will be set to true, if one of the following values is updated: 795 * <ul> 796 * <li> List of application keys 797 * <li> Server params. 798 * <li> Lock-screen secret. 799 * <li> Lock-screen secret type. 800 * <li> Trusted hardware certificate. 801 * </ul> 802 * 803 * @param userId The userId of the profile the application is running under. 804 * @param uid The uid of the application. 805 * @param pending Should create snapshot flag. 806 * @return The primary key of the inserted row, or -1 if failed. 807 * 808 * @hide 809 */ 810 public long setShouldCreateSnapshot(int userId, int uid, boolean pending) { 811 return setLong(userId, uid, 812 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT, pending ? 1 : 0); 813 } 814 815 /** 816 * Returns {@code true} if new snapshot should be created. 817 * Returns {@code false} if the flag was never set. 818 * 819 * @param userId The userId of the profile the application is running under. 820 * @param uid The uid of the application who initialized the local recovery components. 821 * @return should create snapshot flag 822 * 823 * @hide 824 */ 825 public boolean getShouldCreateSnapshot(int userId, int uid) { 826 Long res = getLong(userId, uid, 827 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT); 828 return res != null && res != 0L; 829 } 830 831 832 /** 833 * Returns given long value from the database. 834 * 835 * @param userId The userId of the profile the application is running under. 836 * @param uid The uid of the application who initialized the local recovery components. 837 * @param key from {@code RecoveryServiceMetadataEntry} 838 * @return The value that were previously set, or null if there's none. 839 * 840 * @hide 841 */ 842 private Long getLong(int userId, int uid, String key) { 843 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 844 845 String[] projection = { 846 RecoveryServiceMetadataEntry._ID, 847 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 848 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 849 key}; 850 String selection = 851 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 852 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 853 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 854 855 try ( 856 Cursor cursor = db.query( 857 RecoveryServiceMetadataEntry.TABLE_NAME, 858 projection, 859 selection, 860 selectionArguments, 861 /*groupBy=*/ null, 862 /*having=*/ null, 863 /*orderBy=*/ null) 864 ) { 865 int count = cursor.getCount(); 866 if (count == 0) { 867 return null; 868 } 869 if (count > 1) { 870 Log.wtf(TAG, 871 String.format(Locale.US, 872 "%d entries found for userId=%d uid=%d. " 873 + "Should only ever be 0 or 1.", count, userId, uid)); 874 return null; 875 } 876 cursor.moveToFirst(); 877 int idx = cursor.getColumnIndexOrThrow(key); 878 if (cursor.isNull(idx)) { 879 return null; 880 } else { 881 return cursor.getLong(idx); 882 } 883 } 884 } 885 886 /** 887 * Sets a long value in the database. 888 * 889 * @param userId The userId of the profile the application is running under. 890 * @param uid The uid of the application who initialized the local recovery components. 891 * @param key defined in {@code RecoveryServiceMetadataEntry} 892 * @param value new value. 893 * @return The primary key of the inserted row, or -1 if failed. 894 * 895 * @hide 896 */ 897 898 private long setLong(int userId, int uid, String key, long value) { 899 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 900 ContentValues values = new ContentValues(); 901 values.put(key, value); 902 String selection = 903 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 904 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 905 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 906 907 ensureRecoveryServiceMetadataEntryExists(userId, uid); 908 return db.update( 909 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); 910 } 911 912 /** 913 * Returns given binary value from the database. 914 * 915 * @param userId The userId of the profile the application is running under. 916 * @param uid The uid of the application who initialized the local recovery components. 917 * @param key from {@code RecoveryServiceMetadataEntry} 918 * @return The value that were previously set, or null if there's none. 919 * 920 * @hide 921 */ 922 private byte[] getBytes(int userId, int uid, String key) { 923 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 924 925 String[] projection = { 926 RecoveryServiceMetadataEntry._ID, 927 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, 928 RecoveryServiceMetadataEntry.COLUMN_NAME_UID, 929 key}; 930 String selection = 931 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " 932 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; 933 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; 934 935 try ( 936 Cursor cursor = db.query( 937 RecoveryServiceMetadataEntry.TABLE_NAME, 938 projection, 939 selection, 940 selectionArguments, 941 /*groupBy=*/ null, 942 /*having=*/ null, 943 /*orderBy=*/ null) 944 ) { 945 int count = cursor.getCount(); 946 if (count == 0) { 947 return null; 948 } 949 if (count > 1) { 950 Log.wtf(TAG, 951 String.format(Locale.US, 952 "%d entries found for userId=%d uid=%d. " 953 + "Should only ever be 0 or 1.", count, userId, uid)); 954 return null; 955 } 956 cursor.moveToFirst(); 957 int idx = cursor.getColumnIndexOrThrow(key); 958 if (cursor.isNull(idx)) { 959 return null; 960 } else { 961 return cursor.getBlob(idx); 962 } 963 } 964 } 965 966 /** 967 * Sets a binary value in the database. 968 * 969 * @param userId The userId of the profile the application is running under. 970 * @param uid The uid of the application who initialized the local recovery components. 971 * @param key defined in {@code RecoveryServiceMetadataEntry} 972 * @param value new value. 973 * @return The primary key of the inserted row, or -1 if failed. 974 * 975 * @hide 976 */ 977 private long setBytes(int userId, int uid, String key, byte[] 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 rootAlias The root of trust alias. 997 * @param key from {@code RootOfTrustEntry} 998 * @return The value that were previously set, or null if there's none. 999 * 1000 * @hide 1001 */ 1002 private byte[] getBytes(int userId, int uid, String rootAlias, String key) { 1003 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1004 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 1005 1006 String[] projection = { 1007 RootOfTrustEntry._ID, 1008 RootOfTrustEntry.COLUMN_NAME_USER_ID, 1009 RootOfTrustEntry.COLUMN_NAME_UID, 1010 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, 1011 key}; 1012 String selection = 1013 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1014 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1015 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1016 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1017 1018 try ( 1019 Cursor cursor = db.query( 1020 RootOfTrustEntry.TABLE_NAME, 1021 projection, 1022 selection, 1023 selectionArguments, 1024 /*groupBy=*/ null, 1025 /*having=*/ null, 1026 /*orderBy=*/ null) 1027 ) { 1028 int count = cursor.getCount(); 1029 if (count == 0) { 1030 return null; 1031 } 1032 if (count > 1) { 1033 Log.wtf(TAG, 1034 String.format(Locale.US, 1035 "%d entries found for userId=%d uid=%d. " 1036 + "Should only ever be 0 or 1.", count, userId, uid)); 1037 return null; 1038 } 1039 cursor.moveToFirst(); 1040 int idx = cursor.getColumnIndexOrThrow(key); 1041 if (cursor.isNull(idx)) { 1042 return null; 1043 } else { 1044 return cursor.getBlob(idx); 1045 } 1046 } 1047 } 1048 1049 /** 1050 * Sets a binary value in the database. 1051 * 1052 * @param userId The userId of the profile the application is running under. 1053 * @param uid The uid of the application who initialized the local recovery components. 1054 * @param rootAlias The root of trust alias. 1055 * @param key defined in {@code RootOfTrustEntry} 1056 * @param value new value. 1057 * @return The primary key of the inserted row, or -1 if failed. 1058 * 1059 * @hide 1060 */ 1061 private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) { 1062 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1063 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1064 ContentValues values = new ContentValues(); 1065 values.put(key, value); 1066 String selection = 1067 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1068 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1069 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1070 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1071 1072 ensureRootOfTrustEntryExists(userId, uid, rootAlias); 1073 return db.update( 1074 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments); 1075 } 1076 1077 /** 1078 * Returns given long value from the database. 1079 * 1080 * @param userId The userId of the profile the application is running under. 1081 * @param uid The uid of the application who initialized the local recovery components. 1082 * @param rootAlias The root of trust alias. 1083 * @param key from {@code RootOfTrustEntry} 1084 * @return The value that were previously set, or null if there's none. 1085 * 1086 * @hide 1087 */ 1088 private Long getLong(int userId, int uid, String rootAlias, String key) { 1089 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1090 SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); 1091 1092 String[] projection = { 1093 RootOfTrustEntry._ID, 1094 RootOfTrustEntry.COLUMN_NAME_USER_ID, 1095 RootOfTrustEntry.COLUMN_NAME_UID, 1096 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, 1097 key}; 1098 String selection = 1099 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1100 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1101 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1102 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1103 1104 try ( 1105 Cursor cursor = db.query( 1106 RootOfTrustEntry.TABLE_NAME, 1107 projection, 1108 selection, 1109 selectionArguments, 1110 /*groupBy=*/ null, 1111 /*having=*/ null, 1112 /*orderBy=*/ null) 1113 ) { 1114 int count = cursor.getCount(); 1115 if (count == 0) { 1116 return null; 1117 } 1118 if (count > 1) { 1119 Log.wtf(TAG, 1120 String.format(Locale.US, 1121 "%d entries found for userId=%d uid=%d. " 1122 + "Should only ever be 0 or 1.", count, userId, uid)); 1123 return null; 1124 } 1125 cursor.moveToFirst(); 1126 int idx = cursor.getColumnIndexOrThrow(key); 1127 if (cursor.isNull(idx)) { 1128 return null; 1129 } else { 1130 return cursor.getLong(idx); 1131 } 1132 } 1133 } 1134 1135 /** 1136 * Sets a long value in the database. 1137 * 1138 * @param userId The userId of the profile the application is running under. 1139 * @param uid The uid of the application who initialized the local recovery components. 1140 * @param rootAlias The root of trust alias. 1141 * @param key defined in {@code RootOfTrustEntry} 1142 * @param value new value. 1143 * @return The primary key of the inserted row, or -1 if failed. 1144 * 1145 * @hide 1146 */ 1147 1148 private long setLong(int userId, int uid, String rootAlias, String key, long value) { 1149 rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias); 1150 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1151 ContentValues values = new ContentValues(); 1152 values.put(key, value); 1153 String selection = 1154 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND " 1155 + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND " 1156 + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?"; 1157 String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias}; 1158 1159 ensureRootOfTrustEntryExists(userId, uid, rootAlias); 1160 return db.update( 1161 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments); 1162 } 1163 1164 1165 /** 1166 * Creates an empty row in the recovery service metadata table if such a row doesn't exist for 1167 * the given userId and uid, so db.update will succeed. 1168 */ 1169 private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) { 1170 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1171 ContentValues values = new ContentValues(); 1172 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId); 1173 values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid); 1174 db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, 1175 values, SQLiteDatabase.CONFLICT_IGNORE); 1176 } 1177 1178 /** 1179 * Creates an empty row in the root of trust table if such a row doesn't exist for 1180 * the given userId and uid, so db.update will succeed. 1181 */ 1182 private void ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias) { 1183 SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); 1184 ContentValues values = new ContentValues(); 1185 values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, userId); 1186 values.put(RootOfTrustEntry.COLUMN_NAME_UID, uid); 1187 values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, rootAlias); 1188 db.insertWithOnConflict(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null, 1189 values, SQLiteDatabase.CONFLICT_IGNORE); 1190 } 1191 1192 /** 1193 * Closes all open connections to the database. 1194 */ 1195 public void close() { 1196 mKeyStoreDbHelper.close(); 1197 } 1198 1199 @Nullable 1200 private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException { 1201 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes); 1202 try { 1203 return KeyFactory.getInstance("EC").generatePublic(publicKeySpec); 1204 } catch (NoSuchAlgorithmException e) { 1205 // Should never happen 1206 throw new RuntimeException(e); 1207 } 1208 } 1209 1210 @Nullable 1211 private static CertPath decodeCertPath(byte[] bytes) throws CertificateException { 1212 CertificateFactory certFactory; 1213 try { 1214 certFactory = CertificateFactory.getInstance("X.509"); 1215 } catch (CertificateException e) { 1216 // Should not happen, as X.509 is mandatory for all providers. 1217 throw new RuntimeException(e); 1218 } 1219 return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING); 1220 } 1221 } 1222