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