1 /* 2 * Copyright 2020 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.bluetooth.btservice.bluetoothkeystore; 18 19 import android.annotation.Nullable; 20 import android.os.SystemProperties; 21 import android.security.keystore.KeyGenParameterSpec; 22 import android.security.keystore.KeyProperties; 23 import android.util.Log; 24 25 import com.android.bluetooth.BluetoothKeystoreProto; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import com.google.protobuf.ByteString; 29 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.nio.file.Files; 33 import java.nio.file.Paths; 34 import java.nio.file.StandardCopyOption; 35 import java.security.InvalidAlgorithmParameterException; 36 import java.security.InvalidKeyException; 37 import java.security.KeyStore; 38 import java.security.KeyStoreException; 39 import java.security.MessageDigest; 40 import java.security.NoSuchAlgorithmException; 41 import java.security.NoSuchProviderException; 42 import java.security.ProviderException; 43 import java.security.UnrecoverableEntryException; 44 import java.security.cert.CertificateException; 45 import java.util.Base64; 46 import java.util.HashMap; 47 import java.util.LinkedList; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.concurrent.BlockingQueue; 52 import java.util.concurrent.LinkedBlockingQueue; 53 54 import javax.crypto.BadPaddingException; 55 import javax.crypto.Cipher; 56 import javax.crypto.IllegalBlockSizeException; 57 import javax.crypto.KeyGenerator; 58 import javax.crypto.NoSuchPaddingException; 59 import javax.crypto.SecretKey; 60 import javax.crypto.spec.GCMParameterSpec; 61 62 /** 63 * Service used for handling encryption and decryption of the bt_config.conf 64 */ 65 public class BluetoothKeystoreService { 66 private static final String TAG = "BluetoothKeystoreService"; 67 68 private static final boolean DBG = false; 69 70 private static BluetoothKeystoreService sBluetoothKeystoreService; 71 private boolean mCleaningUp; 72 private boolean mIsCommonCriteriaMode; 73 74 private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; 75 private static final int GCM_TAG_LENGTH = 128; 76 private static final int KEY_LENGTH = 256; 77 private static final String KEYALIAS = "bluetooth-key-encrypted"; 78 private static final String KEY_STORE = "AndroidKeyStore"; 79 private static final int TRY_MAX = 3; 80 81 private static final String CONFIG_FILE_PREFIX = "bt_config-origin"; 82 private static final String CONFIG_BACKUP_PREFIX = "bt_config-backup"; 83 84 private static final String CONFIG_FILE_HASH = "hash"; 85 86 private static final String CONFIG_CHECKSUM_ENCRYPTION_PATH = 87 "/data/misc/bluedroid/bt_config.checksum.encrypted"; 88 private static final String CONFIG_FILE_ENCRYPTION_PATH = 89 "/data/misc/bluedroid/bt_config.conf.encrypted"; 90 private static final String CONFIG_BACKUP_ENCRYPTION_PATH = 91 "/data/misc/bluedroid/bt_config.bak.encrypted"; 92 93 private static final String CONFIG_FILE_PATH = "/data/misc/bluedroid/bt_config.conf"; 94 private static final String CONFIG_BACKUP_PATH = "/data/misc/bluedroid/bt_config.bak"; 95 private static final String CONFIG_FILE_CHECKSUM_PATH = 96 "/data/misc/bluedroid/bt_config.conf.encrypted-checksum"; 97 private static final String CONFIG_BACKUP_CHECKSUM_PATH = 98 "/data/misc/bluedroid/bt_config.bak.encrypted-checksum"; 99 100 private static final int BUFFER_SIZE = 400 * 10; 101 102 private static final int CONFIG_COMPARE_INIT = 0b00; 103 private static final int CONFIG_FILE_COMPARE_PASS = 0b01; 104 private static final int CONFIG_BACKUP_COMPARE_PASS = 0b10; 105 private int mCompareResult; 106 107 BluetoothKeystoreNativeInterface mBluetoothKeystoreNativeInterface; 108 109 private ComputeDataThread mEncryptDataThread; 110 private ComputeDataThread mDecryptDataThread; 111 private Map<String, String> mNameEncryptKey = new HashMap<>(); 112 private Map<String, String> mNameDecryptKey = new HashMap<>(); 113 private BlockingQueue<String> mPendingDecryptKey = new LinkedBlockingQueue<>(); 114 private BlockingQueue<String> mPendingEncryptKey = new LinkedBlockingQueue<>(); 115 private final List<String> mEncryptKeyNameList = List.of("LinkKey", "LE_KEY_PENC", "LE_KEY_PID", 116 "LE_KEY_LID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"); 117 118 private Base64.Decoder mDecoder = Base64.getDecoder(); 119 private Base64.Encoder mEncoder = Base64.getEncoder(); 120 BluetoothKeystoreService(boolean isCommonCriteriaMode)121 public BluetoothKeystoreService(boolean isCommonCriteriaMode) { 122 debugLog("new BluetoothKeystoreService isCommonCriteriaMode: " + isCommonCriteriaMode); 123 mIsCommonCriteriaMode = isCommonCriteriaMode; 124 mCompareResult = CONFIG_COMPARE_INIT; 125 startThread(); 126 } 127 128 /** 129 * Start and initialize the BluetoothKeystoreService 130 */ start()131 public void start() { 132 debugLog("start"); 133 KeyStore keyStore; 134 135 if (sBluetoothKeystoreService != null) { 136 errorLog("start() called twice"); 137 return; 138 } 139 140 keyStore = getKeyStore(); 141 142 // Confirm whether to enable Common Criteria mode for the first time. 143 if (keyStore == null) { 144 debugLog("cannot find the keystore."); 145 return; 146 } 147 148 mBluetoothKeystoreNativeInterface = Objects.requireNonNull( 149 BluetoothKeystoreNativeInterface.getInstance(), 150 "BluetoothKeystoreNativeInterface cannot be null when BluetoothKeystore starts"); 151 152 // Mark service as started 153 setBluetoothKeystoreService(this); 154 155 try { 156 if (!keyStore.containsAlias(KEYALIAS) && mIsCommonCriteriaMode) { 157 infoLog("Enable Common Criteria mode for the first time, pass hash check."); 158 mCompareResult = 0b11; 159 return; 160 } 161 } catch (KeyStoreException e) { 162 reportKeystoreException(e, "cannot find the keystore"); 163 return; 164 } 165 166 loadConfigData(); 167 } 168 169 /** 170 * Factory reset the keystore service. 171 */ factoryReset()172 public void factoryReset() { 173 try { 174 cleanupAll(); 175 } catch (IOException e) { 176 reportBluetoothKeystoreException(e, "IO error while file operating."); 177 } 178 } 179 180 /** 181 * Cleans up the keystore service. 182 */ cleanup()183 public void cleanup() { 184 debugLog("cleanup"); 185 if (mCleaningUp) { 186 debugLog("already doing cleanup"); 187 } 188 189 mCleaningUp = true; 190 191 if (sBluetoothKeystoreService == null) { 192 debugLog("cleanup() called before start()"); 193 return; 194 } 195 // Mark service as stopped 196 setBluetoothKeystoreService(null); 197 198 // Cleanup native interface 199 mBluetoothKeystoreNativeInterface.cleanup(); 200 mBluetoothKeystoreNativeInterface = null; 201 202 if (mIsCommonCriteriaMode) { 203 cleanupForCommonCriteriaModeEnable(); 204 } else { 205 cleanupForCommonCriteriaModeDisable(); 206 } 207 } 208 209 /** 210 * Clean up if Common Criteria mode is enabled. 211 */ 212 @VisibleForTesting cleanupForCommonCriteriaModeEnable()213 public void cleanupForCommonCriteriaModeEnable() { 214 try { 215 setEncryptKeyOrRemoveKey(CONFIG_FILE_PREFIX, CONFIG_FILE_HASH); 216 } catch (InterruptedException e) { 217 reportBluetoothKeystoreException(e, "Interrupted while operating."); 218 } catch (IOException e) { 219 reportBluetoothKeystoreException(e, "IO error while file operating."); 220 } catch (NoSuchAlgorithmException e) { 221 reportBluetoothKeystoreException(e, "encrypt could not find the algorithm: SHA256"); 222 } 223 cleanupMemory(); 224 stopThread(); 225 } 226 227 /** 228 * Clean up if Common Criteria mode is disabled. 229 */ 230 @VisibleForTesting cleanupForCommonCriteriaModeDisable()231 public void cleanupForCommonCriteriaModeDisable() { 232 mNameDecryptKey.clear(); 233 mNameEncryptKey.clear(); 234 } 235 236 /** 237 * Load decryption data from file. 238 */ 239 @VisibleForTesting loadConfigData()240 public void loadConfigData() { 241 try { 242 debugLog("loadConfigData"); 243 244 if (isFactoryReset()) { 245 cleanupAll(); 246 } 247 248 if (Files.exists(Paths.get(CONFIG_CHECKSUM_ENCRYPTION_PATH))) { 249 debugLog("Load encryption file."); 250 // Step2: Load checksum file. 251 loadEncryptionFile(CONFIG_CHECKSUM_ENCRYPTION_PATH, false); 252 // Step3: Compare hash file. 253 if (compareFileHash(CONFIG_FILE_PATH)) { 254 debugLog("bt_config.conf checksum pass."); 255 mCompareResult = mCompareResult | CONFIG_FILE_COMPARE_PASS; 256 } 257 if (compareFileHash(CONFIG_BACKUP_PATH)) { 258 debugLog("bt_config.bak checksum pass."); 259 mCompareResult = mCompareResult | CONFIG_BACKUP_COMPARE_PASS; 260 } 261 // Step4: choose which encryption file loads. 262 if (doesComparePass(CONFIG_FILE_COMPARE_PASS)) { 263 loadEncryptionFile(CONFIG_FILE_ENCRYPTION_PATH, true); 264 } else if (doesComparePass(CONFIG_BACKUP_COMPARE_PASS)) { 265 Files.deleteIfExists(Paths.get(CONFIG_FILE_ENCRYPTION_PATH)); 266 mNameEncryptKey.remove(CONFIG_FILE_PREFIX); 267 loadEncryptionFile(CONFIG_BACKUP_ENCRYPTION_PATH, true); 268 } else { 269 // if the Common Criteria mode is disable, don't show the log. 270 if (mIsCommonCriteriaMode) { 271 debugLog("Config file conf and bak checksum check fail."); 272 } 273 cleanupAll(); 274 return; 275 } 276 } 277 // keep memory data for get decrypted key if Common Criteria mode disable. 278 if (!mIsCommonCriteriaMode) { 279 stopThread(); 280 cleanupFile(); 281 } 282 } catch (IOException e) { 283 reportBluetoothKeystoreException(e, "IO error while file operating."); 284 } catch (InterruptedException e) { 285 reportBluetoothKeystoreException(e, "Interrupted while operating."); 286 } catch (NoSuchAlgorithmException e) { 287 reportBluetoothKeystoreException(e, "could not find the algorithm: SHA256"); 288 } 289 } 290 isFactoryReset()291 private boolean isFactoryReset() { 292 return SystemProperties.getBoolean("persist.bluetooth.factoryreset", false); 293 } 294 295 /** 296 * Init JNI 297 */ initJni()298 public void initJni() { 299 debugLog("initJni()"); 300 // Need to make sure all keys are decrypted. 301 stopThread(); 302 startThread(); 303 // Initialize native interface 304 if (mBluetoothKeystoreNativeInterface != null) { 305 mBluetoothKeystoreNativeInterface.init(); 306 } 307 } 308 isAvailable()309 private boolean isAvailable() { 310 return !mCleaningUp; 311 } 312 313 /** 314 * Get the BluetoothKeystoreService instance 315 */ getBluetoothKeystoreService()316 public static synchronized BluetoothKeystoreService getBluetoothKeystoreService() { 317 if (sBluetoothKeystoreService == null) { 318 debugLog("getBluetoothKeystoreService(): service is NULL"); 319 return null; 320 } 321 322 if (!sBluetoothKeystoreService.isAvailable()) { 323 debugLog("getBluetoothKeystoreService(): service is not available"); 324 return null; 325 } 326 return sBluetoothKeystoreService; 327 } 328 setBluetoothKeystoreService( BluetoothKeystoreService instance)329 private static synchronized void setBluetoothKeystoreService( 330 BluetoothKeystoreService instance) { 331 debugLog("setBluetoothKeystoreService(): set to: " + instance); 332 sBluetoothKeystoreService = instance; 333 } 334 335 /** 336 * Gets result of the checksum comparison 337 */ getCompareResult()338 public int getCompareResult() { 339 debugLog("getCompareResult: " + mCompareResult); 340 return mCompareResult; 341 } 342 343 /** 344 * Sets or removes the encryption key value. 345 * 346 * <p>If the value of decryptedString matches {@link #CONFIG_FILE_HASH} then 347 * read the hash file and decrypt the keys and place them into {@link mPendingEncryptKey} 348 * otherwise cleanup all data and remove the keys. 349 * 350 * @param prefixString key to use 351 * @param decryptedString string to decrypt 352 */ setEncryptKeyOrRemoveKey(String prefixString, String decryptedString)353 public void setEncryptKeyOrRemoveKey(String prefixString, String decryptedString) 354 throws InterruptedException, IOException, NoSuchAlgorithmException { 355 infoLog("setEncryptKeyOrRemoveKey: prefix: " + prefixString); 356 if (prefixString == null || decryptedString == null) { 357 return; 358 } 359 if (prefixString.equals(CONFIG_FILE_PREFIX)) { 360 if (decryptedString.isEmpty()) { 361 cleanupAll(); 362 } else if (decryptedString.equals(CONFIG_FILE_HASH)) { 363 readHashFile(CONFIG_FILE_PATH, CONFIG_FILE_PREFIX); 364 mPendingEncryptKey.put(CONFIG_FILE_PREFIX); 365 readHashFile(CONFIG_BACKUP_PATH, CONFIG_BACKUP_PREFIX); 366 mPendingEncryptKey.put(CONFIG_BACKUP_PREFIX); 367 saveEncryptedKey(); 368 } 369 return; 370 } 371 372 if (decryptedString.isEmpty()) { 373 // clear the item by prefixString. 374 mNameDecryptKey.remove(prefixString); 375 mNameEncryptKey.remove(prefixString); 376 } else { 377 mNameDecryptKey.put(prefixString, decryptedString); 378 mPendingEncryptKey.put(prefixString); 379 } 380 } 381 382 /** 383 * Clean up memory and all files. 384 */ 385 @VisibleForTesting cleanupAll()386 public void cleanupAll() throws IOException { 387 cleanupFile(); 388 cleanupMemory(); 389 } 390 cleanupFile()391 private void cleanupFile() throws IOException { 392 Files.deleteIfExists(Paths.get(CONFIG_CHECKSUM_ENCRYPTION_PATH)); 393 Files.deleteIfExists(Paths.get(CONFIG_FILE_ENCRYPTION_PATH)); 394 Files.deleteIfExists(Paths.get(CONFIG_BACKUP_ENCRYPTION_PATH)); 395 } 396 397 /** 398 * Clean up memory. 399 */ 400 @VisibleForTesting cleanupMemory()401 public void cleanupMemory() { 402 stopThread(); 403 mNameEncryptKey.clear(); 404 mNameDecryptKey.clear(); 405 startThread(); 406 } 407 408 /** 409 * Stop encrypt/decrypt thread. 410 */ 411 @VisibleForTesting stopThread()412 public void stopThread() { 413 try { 414 if (mEncryptDataThread != null) { 415 mEncryptDataThread.setWaitQueueEmptyForStop(); 416 mEncryptDataThread.join(); 417 } 418 if (mDecryptDataThread != null) { 419 mDecryptDataThread.setWaitQueueEmptyForStop(); 420 mDecryptDataThread.join(); 421 } 422 } catch (InterruptedException e) { 423 reportBluetoothKeystoreException(e, "Interrupted while operating."); 424 } 425 } 426 startThread()427 private void startThread() { 428 mEncryptDataThread = new ComputeDataThread(true); 429 mDecryptDataThread = new ComputeDataThread(false); 430 mEncryptDataThread.start(); 431 mDecryptDataThread.start(); 432 } 433 434 /** 435 * Get key value from the mNameDecryptKey. 436 */ getKey(String prefixString)437 public String getKey(String prefixString) { 438 infoLog("getKey: prefix: " + prefixString); 439 if (!mNameDecryptKey.containsKey(prefixString)) { 440 return null; 441 } 442 443 return mNameDecryptKey.get(prefixString); 444 } 445 446 /** 447 * Save encryption key into the encryption file. 448 */ 449 @VisibleForTesting saveEncryptedKey()450 public void saveEncryptedKey() { 451 stopThread(); 452 List<String> configEncryptedLines = new LinkedList<>(); 453 List<String> keyEncryptedLines = new LinkedList<>(); 454 for (String key : mNameEncryptKey.keySet()) { 455 if (key.equals(CONFIG_FILE_PREFIX) || key.equals(CONFIG_BACKUP_PREFIX)) { 456 configEncryptedLines.add(getEncryptedKeyData(key)); 457 } else { 458 keyEncryptedLines.add(getEncryptedKeyData(key)); 459 } 460 } 461 startThread(); 462 463 try { 464 if (!configEncryptedLines.isEmpty()) { 465 Files.write(Paths.get(CONFIG_CHECKSUM_ENCRYPTION_PATH), configEncryptedLines); 466 } 467 if (!keyEncryptedLines.isEmpty()) { 468 Files.write(Paths.get(CONFIG_FILE_ENCRYPTION_PATH), keyEncryptedLines); 469 Files.write(Paths.get(CONFIG_BACKUP_ENCRYPTION_PATH), keyEncryptedLines); 470 } 471 } catch (IOException e) { 472 throw new RuntimeException("write encryption file fail"); 473 } 474 } 475 getEncryptedKeyData(String prefixString)476 private String getEncryptedKeyData(String prefixString) { 477 if (prefixString == null) { 478 return null; 479 } 480 return prefixString.concat("-").concat(mNameEncryptKey.get(prefixString)); 481 } 482 483 /* 484 * Get the mNameEncryptKey hashMap. 485 */ 486 @VisibleForTesting getNameEncryptKey()487 public Map<String, String> getNameEncryptKey() { 488 return mNameEncryptKey; 489 } 490 491 /* 492 * Get the mNameDecryptKey hashMap. 493 */ 494 @VisibleForTesting getNameDecryptKey()495 public Map<String, String> getNameDecryptKey() { 496 return mNameDecryptKey; 497 } 498 doesComparePass(int item)499 private boolean doesComparePass(int item) { 500 return (mCompareResult & item) == item; 501 } 502 503 /** 504 * Compare config file checksum. 505 */ 506 @VisibleForTesting compareFileHash(String hashFilePathString)507 public boolean compareFileHash(String hashFilePathString) 508 throws InterruptedException, IOException, NoSuchAlgorithmException { 509 if (!Files.exists(Paths.get(hashFilePathString))) { 510 infoLog("compareFileHash: File does not exist, path: " + hashFilePathString); 511 return false; 512 } 513 514 String prefixString = null; 515 if (CONFIG_FILE_PATH.equals(hashFilePathString)) { 516 prefixString = CONFIG_FILE_PREFIX; 517 } else if (CONFIG_BACKUP_PATH.equals(hashFilePathString)) { 518 prefixString = CONFIG_BACKUP_PREFIX; 519 } 520 if (prefixString == null) { 521 errorLog("compareFileHash: Unexpected hash file path: " + hashFilePathString); 522 return false; 523 } 524 525 readHashFile(hashFilePathString, prefixString); 526 527 if (!mNameEncryptKey.containsKey(prefixString)) { 528 errorLog("compareFileHash: NameEncryptKey doesn't contain the key, prefix:" 529 + prefixString); 530 return false; 531 } 532 String encryptedData = mNameEncryptKey.get(prefixString); 533 String decryptedData = tryCompute(encryptedData, false); 534 if (decryptedData == null) { 535 errorLog("compareFileHash: decrypt encrypted hash data fail, prefix: " + prefixString); 536 return false; 537 } 538 539 return decryptedData.equals(mNameDecryptKey.get(prefixString)); 540 } 541 readHashFile(String filePathString, String prefixString)542 private void readHashFile(String filePathString, String prefixString) 543 throws InterruptedException, NoSuchAlgorithmException { 544 byte[] dataBuffer = new byte[BUFFER_SIZE]; 545 int bytesRead = 0; 546 boolean successful = false; 547 int counter = 0; 548 while (!successful && counter < TRY_MAX) { 549 try { 550 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 551 InputStream fileStream = Files.newInputStream(Paths.get(filePathString)); 552 while ((bytesRead = fileStream.read(dataBuffer)) != -1) { 553 messageDigest.update(dataBuffer, 0, bytesRead); 554 } 555 556 byte[] messageDigestBytes = messageDigest.digest(); 557 StringBuffer hashString = new StringBuffer(); 558 for (int index = 0; index < messageDigestBytes.length; index++) { 559 hashString.append(Integer.toString(( 560 messageDigestBytes[index] & 0xff) + 0x100, 16).substring(1)); 561 } 562 563 mNameDecryptKey.put(prefixString, hashString.toString()); 564 successful = true; 565 } catch (IOException e) { 566 infoLog("Fail to open file, try again. counter: " + counter); 567 Thread.sleep(50); 568 counter++; 569 } 570 } 571 if (counter > 3) { 572 errorLog("Fail to open file"); 573 } 574 } 575 readChecksumFile(String filePathString, String prefixString)576 private void readChecksumFile(String filePathString, String prefixString) throws IOException { 577 if (!Files.exists(Paths.get(filePathString))) { 578 return; 579 } 580 byte[] allBytes = Files.readAllBytes(Paths.get(filePathString)); 581 String checksumDataBase64 = mEncoder.encodeToString(allBytes); 582 mNameEncryptKey.put(prefixString, checksumDataBase64); 583 } 584 585 /** 586 * Parses a file to search for the key and put it into the pending compute queue 587 */ 588 @VisibleForTesting parseConfigFile(String filePathString)589 public void parseConfigFile(String filePathString) 590 throws IOException, InterruptedException { 591 String prefixString = null; 592 String dataString = null; 593 String name = null; 594 String key = null; 595 int index; 596 597 if (!Files.exists(Paths.get(filePathString))) { 598 return; 599 } 600 List<String> allLinesString = Files.readAllLines(Paths.get(filePathString)); 601 for (String line : allLinesString) { 602 if (line.startsWith("[")) { 603 name = line.replace("[", "").replace("]", ""); 604 continue; 605 } 606 607 index = line.indexOf(" = "); 608 if (index < 0) { 609 continue; 610 } 611 key = line.substring(0, index); 612 613 if (!mEncryptKeyNameList.contains(key)) { 614 continue; 615 } 616 617 if (name == null) { 618 continue; 619 } 620 621 prefixString = name + "-" + key; 622 dataString = line.substring(index + 3); 623 if (dataString.length() == 0) { 624 continue; 625 } 626 627 mNameDecryptKey.put(prefixString, dataString); 628 mPendingEncryptKey.put(prefixString); 629 } 630 } 631 632 /** 633 * Load encryption file and push into mNameEncryptKey and pendingDecryptKey. 634 */ 635 @VisibleForTesting loadEncryptionFile(String filePathString, boolean doDecrypt)636 public void loadEncryptionFile(String filePathString, boolean doDecrypt) 637 throws InterruptedException { 638 try { 639 if (!Files.exists(Paths.get(filePathString))) { 640 return; 641 } 642 List<String> allLinesString = Files.readAllLines(Paths.get(filePathString)); 643 for (String line : allLinesString) { 644 int index = line.lastIndexOf("-"); 645 if (index < 0) { 646 continue; 647 } 648 String prefixString = line.substring(0, index); 649 String encryptedString = line.substring(index + 1); 650 651 mNameEncryptKey.put(prefixString, encryptedString); 652 if (doDecrypt) { 653 mPendingDecryptKey.put(prefixString); 654 } 655 } 656 } catch (IOException e) { 657 throw new RuntimeException("read encryption file all line fail"); 658 } 659 } 660 661 // will retry TRY_MAX times. tryCompute(String sourceData, boolean doEncrypt)662 private String tryCompute(String sourceData, boolean doEncrypt) { 663 int counter = 0; 664 String targetData = null; 665 666 if (sourceData == null) { 667 return null; 668 } 669 670 while (targetData == null && counter < TRY_MAX) { 671 if (doEncrypt) { 672 targetData = encrypt(sourceData); 673 } else { 674 targetData = decrypt(sourceData); 675 } 676 counter++; 677 } 678 return targetData; 679 } 680 681 /** 682 * Encrypt the provided data blob. 683 * 684 * @param data String to be encrypted. 685 * @return String as base64. 686 */ encrypt(String data)687 private @Nullable String encrypt(String data) { 688 BluetoothKeystoreProto.EncryptedData protobuf; 689 byte[] outputBytes; 690 String outputBase64 = null; 691 692 try { 693 if (data == null) { 694 errorLog("encrypt: data is null"); 695 return outputBase64; 696 } 697 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 698 SecretKey secretKeyReference = getOrCreateSecretKey(); 699 700 if (secretKeyReference != null) { 701 cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference); 702 protobuf = BluetoothKeystoreProto.EncryptedData.newBuilder() 703 .setEncryptedData(ByteString.copyFrom(cipher.doFinal(data.getBytes()))) 704 .setInitVector(ByteString.copyFrom(cipher.getIV())).build(); 705 706 outputBytes = protobuf.toByteArray(); 707 if (outputBytes == null) { 708 errorLog("encrypt: Failed to serialize EncryptedData protobuf."); 709 return outputBase64; 710 } 711 outputBase64 = mEncoder.encodeToString(outputBytes); 712 } else { 713 errorLog("encrypt: secretKeyReference is null."); 714 } 715 } catch (NoSuchAlgorithmException e) { 716 reportKeystoreException(e, "encrypt could not find the algorithm: " + CIPHER_ALGORITHM); 717 } catch (NoSuchPaddingException e) { 718 reportKeystoreException(e, "encrypt had a padding exception"); 719 } catch (InvalidKeyException e) { 720 reportKeystoreException(e, "encrypt received an invalid key"); 721 } catch (BadPaddingException e) { 722 reportKeystoreException(e, "encrypt had a padding problem"); 723 } catch (IllegalBlockSizeException e) { 724 reportKeystoreException(e, "encrypt had an illegal block size"); 725 } 726 return outputBase64; 727 } 728 729 /** 730 * Decrypt the original data blob from the provided {@link EncryptedData}. 731 * 732 * @param data String as base64 to be decrypted. 733 * @return String. 734 */ decrypt(String encryptedDataBase64)735 private @Nullable String decrypt(String encryptedDataBase64) { 736 BluetoothKeystoreProto.EncryptedData protobuf; 737 byte[] encryptedDataBytes; 738 byte[] decryptedDataBytes; 739 String output = null; 740 741 try { 742 if (encryptedDataBase64 == null) { 743 errorLog("decrypt: encryptedDataBase64 is null"); 744 return output; 745 } 746 encryptedDataBytes = mDecoder.decode(encryptedDataBase64); 747 protobuf = BluetoothKeystoreProto.EncryptedData.parser().parseFrom(encryptedDataBytes); 748 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 749 GCMParameterSpec spec = 750 new GCMParameterSpec(GCM_TAG_LENGTH, protobuf.getInitVector().toByteArray()); 751 SecretKey secretKeyReference = getOrCreateSecretKey(); 752 753 if (secretKeyReference != null) { 754 cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec); 755 decryptedDataBytes = cipher.doFinal(protobuf.getEncryptedData().toByteArray()); 756 output = new String(decryptedDataBytes); 757 } else { 758 errorLog("decrypt: secretKeyReference is null."); 759 } 760 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 761 reportBluetoothKeystoreException(e, "decrypt: Failed to parse EncryptedData protobuf."); 762 } catch (NoSuchAlgorithmException e) { 763 reportKeystoreException(e, 764 "decrypt could not find cipher algorithm " + CIPHER_ALGORITHM); 765 } catch (NoSuchPaddingException e) { 766 reportKeystoreException(e, "decrypt could not find padding algorithm"); 767 } catch (IllegalBlockSizeException e) { 768 reportKeystoreException(e, "decrypt had a illegal block size"); 769 } catch (BadPaddingException e) { 770 reportKeystoreException(e, "decrypt had bad padding"); 771 } catch (InvalidKeyException e) { 772 reportKeystoreException(e, "decrypt had an invalid key"); 773 } catch (InvalidAlgorithmParameterException e) { 774 reportKeystoreException(e, "decrypt had an invalid algorithm parameter"); 775 } 776 return output; 777 } 778 getKeyStore()779 private KeyStore getKeyStore() { 780 KeyStore keyStore = null; 781 int counter = 0; 782 783 while ((counter <= TRY_MAX) && (keyStore == null)) { 784 try { 785 keyStore = KeyStore.getInstance("AndroidKeyStore"); 786 keyStore.load(null); 787 } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException 788 | IOException e) { 789 reportKeystoreException(e, "cannot open keystore"); 790 } 791 counter++; 792 } 793 return keyStore; 794 } 795 796 // The getOrGenerate semantic on keystore is not thread safe, need to synchronized it. getOrCreateSecretKey()797 private synchronized SecretKey getOrCreateSecretKey() { 798 SecretKey secretKey = null; 799 try { 800 KeyStore keyStore = getKeyStore(); 801 if (keyStore.containsAlias(KEYALIAS)) { // The key exists in key store. Get the key. 802 KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore 803 .getEntry(KEYALIAS, null); 804 805 if (secretKeyEntry != null) { 806 secretKey = secretKeyEntry.getSecretKey(); 807 } else { 808 errorLog("decrypt: secretKeyEntry is null."); 809 } 810 } else { 811 // The key does not exist in key store. Create the key and store it. 812 KeyGenerator keyGenerator = KeyGenerator 813 .getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE); 814 815 KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEYALIAS, 816 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 817 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 818 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 819 .setKeySize(KEY_LENGTH) 820 .build(); 821 822 keyGenerator.init(keyGenParameterSpec); 823 secretKey = keyGenerator.generateKey(); 824 } 825 } catch (KeyStoreException e) { 826 reportKeystoreException(e, "cannot find the keystore: " + KEY_STORE); 827 } catch (InvalidAlgorithmParameterException e) { 828 reportKeystoreException(e, "getOrCreateSecretKey had an invalid algorithm parameter"); 829 } catch (NoSuchAlgorithmException e) { 830 reportKeystoreException(e, "getOrCreateSecretKey cannot find algorithm"); 831 } catch (NoSuchProviderException e) { 832 reportKeystoreException(e, "getOrCreateSecretKey cannot find crypto provider"); 833 } catch (UnrecoverableEntryException e) { 834 reportKeystoreException(e, 835 "getOrCreateSecretKey had an unrecoverable entry exception."); 836 } catch (ProviderException e) { 837 reportKeystoreException(e, "getOrCreateSecretKey had a provider exception."); 838 } 839 return secretKey; 840 } 841 reportKeystoreException(Exception exception, String error)842 private static void reportKeystoreException(Exception exception, String error) { 843 Log.wtf(TAG, "A keystore error was encountered: " + error, exception); 844 } 845 reportBluetoothKeystoreException(Exception exception, String error)846 private static void reportBluetoothKeystoreException(Exception exception, String error) { 847 Log.wtf(TAG, "A bluetoothkey store error was encountered: " + error, exception); 848 } 849 infoLog(String msg)850 private static void infoLog(String msg) { 851 if (DBG) { 852 Log.i(TAG, msg); 853 } 854 } 855 debugLog(String msg)856 private static void debugLog(String msg) { 857 Log.d(TAG, msg); 858 } 859 errorLog(String msg)860 private static void errorLog(String msg) { 861 Log.e(TAG, msg); 862 } 863 864 /** 865 * A thread that decrypt data if the queue has new decrypt task. 866 */ 867 private class ComputeDataThread extends Thread { 868 private Map<String, String> mSourceDataMap; 869 private Map<String, String> mTargetDataMap; 870 private BlockingQueue<String> mSourceQueue; 871 private boolean mDoEncrypt; 872 873 private boolean mWaitQueueEmptyForStop; 874 ComputeDataThread(boolean doEncrypt)875 ComputeDataThread(boolean doEncrypt) { 876 infoLog("ComputeDataThread: create, doEncrypt: " + doEncrypt); 877 mWaitQueueEmptyForStop = false; 878 mDoEncrypt = doEncrypt; 879 880 if (mDoEncrypt) { 881 mSourceDataMap = mNameDecryptKey; 882 mTargetDataMap = mNameEncryptKey; 883 mSourceQueue = mPendingEncryptKey; 884 } else { 885 mSourceDataMap = mNameEncryptKey; 886 mTargetDataMap = mNameDecryptKey; 887 mSourceQueue = mPendingDecryptKey; 888 } 889 } 890 891 @Override run()892 public void run() { 893 infoLog("ComputeDataThread: run, doEncrypt: " + mDoEncrypt); 894 String prefixString; 895 String sourceData; 896 String targetData; 897 while (!mSourceQueue.isEmpty() || !mWaitQueueEmptyForStop) { 898 try { 899 prefixString = mSourceQueue.take(); 900 if (mSourceDataMap.containsKey(prefixString)) { 901 sourceData = mSourceDataMap.get(prefixString); 902 targetData = tryCompute(sourceData, mDoEncrypt); 903 if (targetData != null) { 904 mTargetDataMap.put(prefixString, targetData); 905 } else { 906 errorLog("Computing of Data failed with prefixString: " + prefixString 907 + ", doEncrypt: " + mDoEncrypt); 908 } 909 } 910 } catch (InterruptedException e) { 911 infoLog("Interrupted while operating."); 912 } 913 } 914 infoLog("ComputeDataThread: Stop, doEncrypt: " + mDoEncrypt); 915 } 916 setWaitQueueEmptyForStop()917 public void setWaitQueueEmptyForStop() { 918 mWaitQueueEmptyForStop = true; 919 if (mPendingEncryptKey.isEmpty()) { 920 interrupt(); 921 } 922 } 923 } 924 } 925