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.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 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 Map<String, String> mNameEncryptKey = new HashMap<>(); 96 private Map<String, String> mNameDecryptKey = new HashMap<>(); 97 private BlockingQueue<String> mPendingDecryptKey = new LinkedBlockingQueue<>(); 98 private 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 Base64.Decoder mDecoder = Base64.getDecoder(); 110 private 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 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 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 hashString.append( 469 Integer.toString((messageDigestBytes[index] & 0xff) + 0x100, 16) 470 .substring(1)); 471 } 472 473 mNameDecryptKey.put(prefixString, hashString.toString()); 474 successful = true; 475 } catch (IOException e) { 476 infoLog("Fail to open file, try again. counter: " + counter); 477 Thread.sleep(50); 478 counter++; 479 } 480 } 481 if (counter > 3) { 482 errorLog("Fail to open file"); 483 } 484 } 485 486 /** Parses a file to search for the key and put it into the pending compute queue */ 487 @VisibleForTesting parseConfigFile(String filePathString)488 public void parseConfigFile(String filePathString) throws IOException, InterruptedException { 489 String prefixString = null; 490 String dataString = null; 491 String name = null; 492 String key = null; 493 int index; 494 495 if (!Files.exists(Paths.get(filePathString))) { 496 return; 497 } 498 List<String> allLinesString = Files.readAllLines(Paths.get(filePathString)); 499 for (String line : allLinesString) { 500 if (line.startsWith("[")) { 501 name = line.replace("[", "").replace("]", ""); 502 continue; 503 } 504 505 index = line.indexOf(" = "); 506 if (index < 0) { 507 continue; 508 } 509 key = line.substring(0, index); 510 511 if (!mEncryptKeyNameList.contains(key)) { 512 continue; 513 } 514 515 if (name == null) { 516 continue; 517 } 518 519 prefixString = name + "-" + key; 520 dataString = line.substring(index + 3); 521 if (dataString.length() == 0) { 522 continue; 523 } 524 525 mNameDecryptKey.put(prefixString, dataString); 526 mPendingEncryptKey.put(prefixString); 527 } 528 } 529 530 /** Load encryption file and push into mNameEncryptKey and pendingDecryptKey. */ 531 @VisibleForTesting loadEncryptionFile(String filePathString, boolean doDecrypt)532 public void loadEncryptionFile(String filePathString, boolean doDecrypt) 533 throws InterruptedException { 534 try { 535 if (!Files.exists(Paths.get(filePathString))) { 536 return; 537 } 538 List<String> allLinesString = Files.readAllLines(Paths.get(filePathString)); 539 for (String line : allLinesString) { 540 int index = line.lastIndexOf("-"); 541 if (index < 0) { 542 continue; 543 } 544 String prefixString = line.substring(0, index); 545 String encryptedString = line.substring(index + 1); 546 547 mNameEncryptKey.put(prefixString, encryptedString); 548 if (doDecrypt) { 549 mPendingDecryptKey.put(prefixString); 550 } 551 } 552 } catch (IOException e) { 553 throw new RuntimeException("read encryption file all line fail"); 554 } 555 } 556 557 // will retry TRY_MAX times. tryCompute(String sourceData, boolean doEncrypt)558 private String tryCompute(String sourceData, boolean doEncrypt) { 559 int counter = 0; 560 String targetData = null; 561 562 if (sourceData == null) { 563 return null; 564 } 565 566 while (targetData == null && counter < TRY_MAX) { 567 if (doEncrypt) { 568 targetData = encrypt(sourceData); 569 } else { 570 targetData = decrypt(sourceData); 571 } 572 counter++; 573 } 574 return targetData; 575 } 576 577 /** 578 * Encrypt the provided data blob. 579 * 580 * @param data String to be encrypted. 581 * @return String as base64. 582 */ encrypt(String data)583 private @Nullable String encrypt(String data) { 584 BluetoothKeystoreProto.EncryptedData protobuf; 585 byte[] outputBytes; 586 String outputBase64 = null; 587 588 try { 589 if (data == null) { 590 errorLog("encrypt: data is null"); 591 return outputBase64; 592 } 593 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 594 SecretKey secretKeyReference = getOrCreateSecretKey(); 595 596 if (secretKeyReference != null) { 597 cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference); 598 protobuf = 599 BluetoothKeystoreProto.EncryptedData.newBuilder() 600 .setEncryptedData( 601 ByteString.copyFrom(cipher.doFinal(data.getBytes()))) 602 .setInitVector(ByteString.copyFrom(cipher.getIV())) 603 .build(); 604 605 outputBytes = protobuf.toByteArray(); 606 if (outputBytes == null) { 607 errorLog("encrypt: Failed to serialize EncryptedData protobuf."); 608 return outputBase64; 609 } 610 outputBase64 = mEncoder.encodeToString(outputBytes); 611 } else { 612 errorLog("encrypt: secretKeyReference is null."); 613 } 614 } catch (NoSuchAlgorithmException e) { 615 reportKeystoreException(e, "encrypt could not find the algorithm: " + CIPHER_ALGORITHM); 616 } catch (NoSuchPaddingException e) { 617 reportKeystoreException(e, "encrypt had a padding exception"); 618 } catch (InvalidKeyException e) { 619 reportKeystoreException(e, "encrypt received an invalid key"); 620 } catch (BadPaddingException e) { 621 reportKeystoreException(e, "encrypt had a padding problem"); 622 } catch (IllegalBlockSizeException e) { 623 reportKeystoreException(e, "encrypt had an illegal block size"); 624 } 625 return outputBase64; 626 } 627 628 /** 629 * Decrypt the original data blob from the provided {@link EncryptedData}. 630 * 631 * @param encryptedDataBase64 String as base64 to be decrypted. 632 * @return String. 633 */ decrypt(String encryptedDataBase64)634 private @Nullable String decrypt(String encryptedDataBase64) { 635 BluetoothKeystoreProto.EncryptedData protobuf; 636 byte[] encryptedDataBytes; 637 byte[] decryptedDataBytes; 638 String output = null; 639 640 try { 641 if (encryptedDataBase64 == null) { 642 errorLog("decrypt: encryptedDataBase64 is null"); 643 return output; 644 } 645 encryptedDataBytes = mDecoder.decode(encryptedDataBase64); 646 protobuf = BluetoothKeystoreProto.EncryptedData.parser().parseFrom(encryptedDataBytes); 647 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 648 GCMParameterSpec spec = 649 new GCMParameterSpec(GCM_TAG_LENGTH, protobuf.getInitVector().toByteArray()); 650 SecretKey secretKeyReference = getOrCreateSecretKey(); 651 652 if (secretKeyReference != null) { 653 cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec); 654 decryptedDataBytes = cipher.doFinal(protobuf.getEncryptedData().toByteArray()); 655 output = new String(decryptedDataBytes); 656 } else { 657 errorLog("decrypt: secretKeyReference is null."); 658 } 659 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 660 reportBluetoothKeystoreException(e, "decrypt: Failed to parse EncryptedData protobuf."); 661 } catch (NoSuchAlgorithmException e) { 662 reportKeystoreException( 663 e, "decrypt could not find cipher algorithm " + CIPHER_ALGORITHM); 664 } catch (NoSuchPaddingException e) { 665 reportKeystoreException(e, "decrypt could not find padding algorithm"); 666 } catch (IllegalBlockSizeException e) { 667 reportKeystoreException(e, "decrypt had a illegal block size"); 668 } catch (BadPaddingException e) { 669 reportKeystoreException(e, "decrypt had bad padding"); 670 } catch (InvalidKeyException e) { 671 reportKeystoreException(e, "decrypt had an invalid key"); 672 } catch (InvalidAlgorithmParameterException e) { 673 reportKeystoreException(e, "decrypt had an invalid algorithm parameter"); 674 } 675 return output; 676 } 677 getKeyStore()678 private KeyStore getKeyStore() { 679 KeyStore keyStore = null; 680 int counter = 0; 681 682 while ((counter <= TRY_MAX) && (keyStore == null)) { 683 try { 684 keyStore = KeyStore.getInstance("AndroidKeyStore"); 685 keyStore.load(null); 686 } catch (KeyStoreException 687 | CertificateException 688 | NoSuchAlgorithmException 689 | IOException e) { 690 reportKeystoreException(e, "cannot open keystore"); 691 } 692 counter++; 693 } 694 return keyStore; 695 } 696 697 // The getOrGenerate semantic on keystore is not thread safe, need to synchronized it. getOrCreateSecretKey()698 private synchronized SecretKey getOrCreateSecretKey() { 699 SecretKey secretKey = null; 700 try { 701 KeyStore keyStore = getKeyStore(); 702 if (keyStore.containsAlias(KEYALIAS)) { // The key exists in key store. Get the key. 703 KeyStore.SecretKeyEntry secretKeyEntry = 704 (KeyStore.SecretKeyEntry) keyStore.getEntry(KEYALIAS, null); 705 706 if (secretKeyEntry != null) { 707 secretKey = secretKeyEntry.getSecretKey(); 708 } else { 709 errorLog("decrypt: secretKeyEntry is null."); 710 } 711 } else { 712 // The key does not exist in key store. Create the key and store it. 713 KeyGenerator keyGenerator = 714 KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE); 715 716 KeyGenParameterSpec keyGenParameterSpec = 717 new KeyGenParameterSpec.Builder( 718 KEYALIAS, 719 KeyProperties.PURPOSE_ENCRYPT 720 | KeyProperties.PURPOSE_DECRYPT) 721 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 722 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 723 .setKeySize(KEY_LENGTH) 724 .build(); 725 726 keyGenerator.init(keyGenParameterSpec); 727 secretKey = keyGenerator.generateKey(); 728 } 729 } catch (KeyStoreException e) { 730 reportKeystoreException(e, "cannot find the keystore: " + KEY_STORE); 731 } catch (InvalidAlgorithmParameterException e) { 732 reportKeystoreException(e, "getOrCreateSecretKey had an invalid algorithm parameter"); 733 } catch (NoSuchAlgorithmException e) { 734 reportKeystoreException(e, "getOrCreateSecretKey cannot find algorithm"); 735 } catch (NoSuchProviderException e) { 736 reportKeystoreException(e, "getOrCreateSecretKey cannot find crypto provider"); 737 } catch (UnrecoverableEntryException e) { 738 reportKeystoreException( 739 e, "getOrCreateSecretKey had an unrecoverable entry exception."); 740 } catch (ProviderException e) { 741 reportKeystoreException(e, "getOrCreateSecretKey had a provider exception."); 742 } 743 return secretKey; 744 } 745 reportKeystoreException(Exception exception, String error)746 private static void reportKeystoreException(Exception exception, String error) { 747 Log.wtf(TAG, "A keystore error was encountered: " + error, exception); 748 } 749 reportBluetoothKeystoreException(Exception exception, String error)750 private static void reportBluetoothKeystoreException(Exception exception, String error) { 751 Log.wtf(TAG, "A bluetoothkey store error was encountered: " + error, exception); 752 } 753 infoLog(String msg)754 private static void infoLog(String msg) { 755 Log.i(TAG, msg); 756 } 757 debugLog(String msg)758 private static void debugLog(String msg) { 759 Log.d(TAG, msg); 760 } 761 errorLog(String msg)762 private static void errorLog(String msg) { 763 Log.e(TAG, msg); 764 } 765 766 /** A thread that decrypt data if the queue has new decrypt task. */ 767 private class ComputeDataThread extends Thread { 768 private Map<String, String> mSourceDataMap; 769 private Map<String, String> mTargetDataMap; 770 private BlockingQueue<String> mSourceQueue; 771 private boolean mDoEncrypt; 772 773 private boolean mWaitQueueEmptyForStop; 774 ComputeDataThread(boolean doEncrypt)775 ComputeDataThread(boolean doEncrypt) { 776 infoLog("ComputeDataThread: create, doEncrypt: " + doEncrypt); 777 mWaitQueueEmptyForStop = false; 778 mDoEncrypt = doEncrypt; 779 780 if (mDoEncrypt) { 781 mSourceDataMap = mNameDecryptKey; 782 mTargetDataMap = mNameEncryptKey; 783 mSourceQueue = mPendingEncryptKey; 784 } else { 785 mSourceDataMap = mNameEncryptKey; 786 mTargetDataMap = mNameDecryptKey; 787 mSourceQueue = mPendingDecryptKey; 788 } 789 } 790 791 @Override run()792 public void run() { 793 infoLog("ComputeDataThread: run, doEncrypt: " + mDoEncrypt); 794 String prefixString; 795 String sourceData; 796 String targetData; 797 while (!mSourceQueue.isEmpty() || !mWaitQueueEmptyForStop) { 798 try { 799 prefixString = mSourceQueue.take(); 800 if (mSourceDataMap.containsKey(prefixString)) { 801 sourceData = mSourceDataMap.get(prefixString); 802 targetData = tryCompute(sourceData, mDoEncrypt); 803 if (targetData != null) { 804 mTargetDataMap.put(prefixString, targetData); 805 } else { 806 errorLog( 807 "Computing of Data failed with prefixString: " 808 + prefixString 809 + ", doEncrypt: " 810 + mDoEncrypt); 811 } 812 } 813 } catch (InterruptedException e) { 814 infoLog("Interrupted while operating."); 815 } 816 } 817 infoLog("ComputeDataThread: Stop, doEncrypt: " + mDoEncrypt); 818 } 819 setWaitQueueEmptyForStop()820 public void setWaitQueueEmptyForStop() { 821 mWaitQueueEmptyForStop = true; 822 if (mPendingEncryptKey.isEmpty()) { 823 interrupt(); 824 } 825 } 826 } 827 } 828