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