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