blob: c838584fbb0ea7d0b907ad559854b7b0784f44c8 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.btservice.bluetoothkeystore;
import android.annotation.Nullable;
import android.os.Process;
import android.os.SystemProperties;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Log;
import com.android.bluetooth.BluetoothKeystoreProto;
import com.android.internal.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.ProviderException;
import java.security.UnrecoverableEntryException;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
/**
* Service used for handling encryption and decryption of the bt_config.conf
*/
public class BluetoothKeystoreService {
private static final String TAG = "BluetoothKeystoreService";
private static final boolean DBG = false;
private static BluetoothKeystoreService sBluetoothKeystoreService;
private boolean mCleaningUp;
private boolean mIsNiapMode;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
private static final int KEY_LENGTH = 256;
private static final String KEYALIAS = "bluetooth-key-encrypted";
private static final String KEY_STORE = "AndroidKeyStore";
private static final int TRY_MAX = 3;
private static final String CONFIG_FILE_PREFIX = "bt_config-origin";
private static final String CONFIG_BACKUP_PREFIX = "bt_config-backup";
private static final String CONFIG_FILE_HASH = "hash";
private static final String CONFIG_CHECKSUM_ENCRYPTION_PATH =
"/data/misc/bluedroid/bt_config.checksum.encrypted";
private static final String CONFIG_FILE_ENCRYPTION_PATH =
"/data/misc/bluedroid/bt_config.conf.encrypted";
private static final String CONFIG_BACKUP_ENCRYPTION_PATH =
"/data/misc/bluedroid/bt_config.bak.encrypted";
private static final String CONFIG_FILE_PATH = "/data/misc/bluedroid/bt_config.conf";
private static final String CONFIG_BACKUP_PATH = "/data/misc/bluedroid/bt_config.bak";
private static final String CONFIG_FILE_CHECKSUM_PATH =
"/data/misc/bluedroid/bt_config.conf.encrypted-checksum";
private static final String CONFIG_BACKUP_CHECKSUM_PATH =
"/data/misc/bluedroid/bt_config.bak.encrypted-checksum";
private static final int BUFFER_SIZE = 400 * 10;
private static final int CONFIG_COMPARE_INIT = 0b00;
private static final int CONFIG_FILE_COMPARE_PASS = 0b01;
private static final int CONFIG_BACKUP_COMPARE_PASS = 0b10;
private int mCompareResult;
BluetoothKeystoreNativeInterface mBluetoothKeystoreNativeInterface;
private ComputeDataThread mEncryptDataThread;
private ComputeDataThread mDecryptDataThread;
private Map<String, String> mNameEncryptKey = new HashMap<>();
private Map<String, String> mNameDecryptKey = new HashMap<>();
private BlockingQueue<String> mPendingDecryptKey = new LinkedBlockingQueue<>();
private BlockingQueue<String> mPendingEncryptKey = new LinkedBlockingQueue<>();
private final List<String> mEncryptKeyNameList = List.of("LinkKey", "LE_KEY_PENC", "LE_KEY_PID",
"LE_KEY_LID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK");
private Base64.Decoder mDecoder = Base64.getDecoder();
private Base64.Encoder mEncoder = Base64.getEncoder();
public BluetoothKeystoreService(boolean isNiapMode) {
debugLog("new BluetoothKeystoreService isNiapMode: " + isNiapMode);
mIsNiapMode = isNiapMode;
mCompareResult = CONFIG_COMPARE_INIT;
startThread();
}
/**
* Start and initialize the BluetoothKeystoreService
*/
public void start() {
debugLog("start");
KeyStore keyStore;
if (sBluetoothKeystoreService != null) {
errorLog("start() called twice");
return;
}
keyStore = getKeyStore();
// Confirm whether to enable NIAP for the first time.
if (keyStore == null) {
debugLog("cannot find the keystore.");
return;
}
mBluetoothKeystoreNativeInterface = Objects.requireNonNull(
BluetoothKeystoreNativeInterface.getInstance(),
"BluetoothKeystoreNativeInterface cannot be null when BluetoothKeystore starts");
// Mark service as started
setBluetoothKeystoreService(this);
try {
if (!keyStore.containsAlias(KEYALIAS) && mIsNiapMode) {
infoLog("Enable NIAP mode for the first time, pass hash check.");
mCompareResult = 0b11;
return;
}
} catch (KeyStoreException e) {
reportKeystoreException(e, "cannot find the keystore");
return;
}
loadConfigData();
}
/**
* Factory reset the keystore service.
*/
public void factoryReset() {
try {
cleanupAll();
} catch (IOException e) {
reportBluetoothKeystoreException(e, "IO error while file operating.");
}
}
/**
* Cleans up the keystore service.
*/
public void cleanup() {
debugLog("cleanup");
if (mCleaningUp) {
debugLog("already doing cleanup");
}
mCleaningUp = true;
if (sBluetoothKeystoreService == null) {
debugLog("cleanup() called before start()");
return;
}
// Mark service as stopped
setBluetoothKeystoreService(null);
// Cleanup native interface
mBluetoothKeystoreNativeInterface.cleanup();
mBluetoothKeystoreNativeInterface = null;
if (mIsNiapMode) {
cleanupForNiapModeEnable();
} else {
cleanupForNiapModeDisable();
}
}
/**
* Clean up if NIAP mode is enabled.
*/
@VisibleForTesting
public void cleanupForNiapModeEnable() {
try {
setEncryptKeyOrRemoveKey(CONFIG_FILE_PREFIX, CONFIG_FILE_HASH);
} catch (InterruptedException e) {
reportBluetoothKeystoreException(e, "Interrupted while operating.");
} catch (IOException e) {
reportBluetoothKeystoreException(e, "IO error while file operating.");
} catch (NoSuchAlgorithmException e) {
reportBluetoothKeystoreException(e, "encrypt could not find the algorithm: SHA256");
}
cleanupMemory();
stopThread();
}
/**
* Clean up if NIAP mode is disabled.
*/
@VisibleForTesting
public void cleanupForNiapModeDisable() {
mNameDecryptKey.clear();
mNameEncryptKey.clear();
}
/**
* Load decryption data from file.
*/
@VisibleForTesting
public void loadConfigData() {
try {
debugLog("loadConfigData");
if (isFactoryReset()) {
cleanupAll();
}
if (Files.exists(Paths.get(CONFIG_CHECKSUM_ENCRYPTION_PATH))) {
debugLog("Load encryption file.");
// Step2: Load checksum file.
loadEncryptionFile(CONFIG_CHECKSUM_ENCRYPTION_PATH, false);
// Step3: Compare hash file.
if (compareFileHash(CONFIG_FILE_PATH)) {
debugLog("bt_config.conf checksum pass.");
mCompareResult = mCompareResult | CONFIG_FILE_COMPARE_PASS;
}
if (compareFileHash(CONFIG_BACKUP_PATH)) {
debugLog("bt_config.bak checksum pass.");
mCompareResult = mCompareResult | CONFIG_BACKUP_COMPARE_PASS;
}
// Step4: choose which encryption file loads.
if (doesComparePass(CONFIG_FILE_COMPARE_PASS)) {
loadEncryptionFile(CONFIG_FILE_ENCRYPTION_PATH, true);
} else if (doesComparePass(CONFIG_BACKUP_COMPARE_PASS)) {
Files.deleteIfExists(Paths.get(CONFIG_FILE_ENCRYPTION_PATH));
mNameEncryptKey.remove(CONFIG_FILE_PREFIX);
loadEncryptionFile(CONFIG_BACKUP_ENCRYPTION_PATH, true);
} else {
// if the NIAP mode is disable, don't show the log.
if (mIsNiapMode) {
debugLog("Config file conf and bak checksum check fail.");
}
cleanupAll();
return;
}
}
// keep memory data for get decrypted key if NIAP mode disable.
if (!mIsNiapMode) {
stopThread();
cleanupFile();
}
} catch (IOException e) {
reportBluetoothKeystoreException(e, "IO error while file operating.");
} catch (InterruptedException e) {
reportBluetoothKeystoreException(e, "Interrupted while operating.");
} catch (NoSuchAlgorithmException e) {
reportBluetoothKeystoreException(e, "could not find the algorithm: SHA256");
}
}
private boolean isFactoryReset() {
return SystemProperties.getBoolean("persist.bluetooth.factoryreset", false);
}
/**
* Init JNI
*/
public void initJni() {
debugLog("initJni()");
// Initialize native interface
mBluetoothKeystoreNativeInterface.init();
}
private boolean isAvailable() {
return !mCleaningUp;
}
/**
* Get the BluetoothKeystoreService instance
*/
public static synchronized BluetoothKeystoreService getBluetoothKeystoreService() {
if (sBluetoothKeystoreService == null) {
debugLog("getBluetoothKeystoreService(): service is NULL");
return null;
}
if (!sBluetoothKeystoreService.isAvailable()) {
debugLog("getBluetoothKeystoreService(): service is not available");
return null;
}
return sBluetoothKeystoreService;
}
private static synchronized void setBluetoothKeystoreService(
BluetoothKeystoreService instance) {
debugLog("setBluetoothKeystoreService(): set to: " + instance);
sBluetoothKeystoreService = instance;
}
/**
* Gets result of the checksum comparison
*/
public int getCompareResult() {
debugLog("getCompareResult: " + mCompareResult);
return mCompareResult;
}
/**
* Sets or removes the encryption key value.
*
* <p>If the value of decryptedString matches {@link #CONFIG_FILE_HASH} then
* read the hash file and decrypt the keys and place them into {@link mPendingEncryptKey}
* otherwise cleanup all data and remove the keys.
*
* @param prefixString key to use
* @param decryptedString string to decrypt
*/
public void setEncryptKeyOrRemoveKey(String prefixString, String decryptedString)
throws InterruptedException, IOException, NoSuchAlgorithmException {
infoLog("setEncryptKeyOrRemoveKey: prefix: " + prefixString);
if (prefixString == null || decryptedString == null) {
return;
}
if (prefixString.equals(CONFIG_FILE_PREFIX)) {
if (decryptedString.isEmpty()) {
cleanupAll();
} else if (decryptedString.equals(CONFIG_FILE_HASH)) {
backupConfigEncryptionFile();
readHashFile(CONFIG_FILE_PATH, CONFIG_FILE_PREFIX);
//save Map
if (mNameDecryptKey.containsKey(CONFIG_FILE_PREFIX)
&& mNameDecryptKey.get(CONFIG_FILE_PREFIX).equals(
mNameDecryptKey.get(CONFIG_BACKUP_PREFIX))) {
infoLog("Since the hash is same with previous, don't need encrypt again.");
} else {
mPendingEncryptKey.put(prefixString);
}
saveEncryptedKey();
}
return;
}
if (decryptedString.isEmpty()) {
// clear the item by prefixString.
mNameDecryptKey.remove(prefixString);
mNameEncryptKey.remove(prefixString);
} else {
mNameDecryptKey.put(prefixString, decryptedString);
mPendingEncryptKey.put(prefixString);
}
}
/**
* Clean up memory and all files.
*/
@VisibleForTesting
public void cleanupAll() throws IOException {
cleanupFile();
cleanupMemory();
}
private void cleanupFile() throws IOException {
Files.deleteIfExists(Paths.get(CONFIG_CHECKSUM_ENCRYPTION_PATH));
Files.deleteIfExists(Paths.get(CONFIG_FILE_ENCRYPTION_PATH));
Files.deleteIfExists(Paths.get(CONFIG_BACKUP_ENCRYPTION_PATH));
}
/**
* Clean up memory.
*/
@VisibleForTesting
public void cleanupMemory() {
stopThread();
mNameEncryptKey.clear();
mNameDecryptKey.clear();
startThread();
}
/**
* Stop encrypt/decrypt thread.
*/
@VisibleForTesting
public void stopThread() {
try {
if (mEncryptDataThread != null) {
mEncryptDataThread.setWaitQueueEmptyForStop();
mEncryptDataThread.join();
}
if (mDecryptDataThread != null) {
mDecryptDataThread.setWaitQueueEmptyForStop();
mDecryptDataThread.join();
}
} catch (InterruptedException e) {
reportBluetoothKeystoreException(e, "Interrupted while operating.");
}
}
private void startThread() {
mEncryptDataThread = new ComputeDataThread(true);
mDecryptDataThread = new ComputeDataThread(false);
mEncryptDataThread.start();
mDecryptDataThread.start();
}
/**
* Get key value from the mNameDecryptKey.
*/
public String getKey(String prefixString) {
infoLog("getKey: prefix: " + prefixString);
if (!mNameDecryptKey.containsKey(prefixString)) {
return null;
}
return mNameDecryptKey.get(prefixString);
}
/**
* Save encryption key into the encryption file.
*/
@VisibleForTesting
public void saveEncryptedKey() {
stopThread();
List<String> configEncryptedLines = new LinkedList<>();
List<String> keyEncryptedLines = new LinkedList<>();
for (String key : mNameEncryptKey.keySet()) {
if (key.equals(CONFIG_FILE_PREFIX) || key.equals(CONFIG_BACKUP_PREFIX)) {
configEncryptedLines.add(getEncryptedKeyData(key));
} else {
keyEncryptedLines.add(getEncryptedKeyData(key));
}
}
startThread();
try {
if (!configEncryptedLines.isEmpty()) {
Files.write(Paths.get(CONFIG_CHECKSUM_ENCRYPTION_PATH), configEncryptedLines);
}
if (!keyEncryptedLines.isEmpty()) {
Files.write(Paths.get(CONFIG_FILE_ENCRYPTION_PATH), keyEncryptedLines);
}
} catch (IOException e) {
throw new RuntimeException("write encryption file fail");
}
}
private String getEncryptedKeyData(String prefixString) {
if (prefixString == null) {
return null;
}
return prefixString.concat("-").concat(mNameEncryptKey.get(prefixString));
}
/*
* Get the mNameEncryptKey hashMap.
*/
@VisibleForTesting
public Map<String, String> getNameEncryptKey() {
return mNameEncryptKey;
}
/*
* Get the mNameDecryptKey hashMap.
*/
@VisibleForTesting
public Map<String, String> getNameDecryptKey() {
return mNameDecryptKey;
}
private void backupConfigEncryptionFile() throws IOException {
if (Files.exists(Paths.get(CONFIG_FILE_ENCRYPTION_PATH))) {
Files.move(Paths.get(CONFIG_FILE_ENCRYPTION_PATH),
Paths.get(CONFIG_BACKUP_ENCRYPTION_PATH),
StandardCopyOption.REPLACE_EXISTING);
}
if (mNameEncryptKey.containsKey(CONFIG_FILE_PREFIX)) {
mNameEncryptKey.put(CONFIG_BACKUP_PREFIX, mNameEncryptKey.get(CONFIG_FILE_PREFIX));
}
if (mNameDecryptKey.containsKey(CONFIG_FILE_PREFIX)) {
mNameDecryptKey.put(CONFIG_BACKUP_PREFIX, mNameDecryptKey.get(CONFIG_FILE_PREFIX));
}
}
private boolean doesComparePass(int item) {
return (mCompareResult & item) == item;
}
/**
* Compare config file checksum.
*/
@VisibleForTesting
public boolean compareFileHash(String hashFilePathString)
throws IOException, NoSuchAlgorithmException {
if (!Files.exists(Paths.get(hashFilePathString))) {
infoLog("compareFileHash: File does not exist, path: " + hashFilePathString);
return false;
}
String prefixString = null;
if (CONFIG_FILE_PATH.equals(hashFilePathString)) {
prefixString = CONFIG_FILE_PREFIX;
} else if (CONFIG_BACKUP_PATH.equals(hashFilePathString)) {
prefixString = CONFIG_BACKUP_PREFIX;
}
if (prefixString == null) {
errorLog("compareFileHash: Unexpected hash file path: " + hashFilePathString);
return false;
}
readHashFile(hashFilePathString, prefixString);
if (!mNameEncryptKey.containsKey(prefixString)) {
errorLog("compareFileHash: NameEncryptKey doesn't contain the key, prefix:"
+ prefixString);
return false;
}
String encryptedData = mNameEncryptKey.get(prefixString);
String decryptedData = tryCompute(encryptedData, false);
if (decryptedData == null) {
errorLog("compareFileHash: decrypt encrypted hash data fail, prefix: " + prefixString);
return false;
}
return decryptedData.equals(mNameDecryptKey.get(prefixString));
}
private void readHashFile(String filePathString, String prefixString)
throws IOException, NoSuchAlgorithmException {
byte[] dataBuffer = new byte[BUFFER_SIZE];
int bytesRead = 0;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
InputStream fileStream = Files.newInputStream(Paths.get(filePathString));
while ((bytesRead = fileStream.read(dataBuffer)) != -1) {
messageDigest.update(dataBuffer, 0, bytesRead);
}
byte[] messageDigestBytes = messageDigest.digest();
StringBuffer hashString = new StringBuffer();
for (int index = 0; index < messageDigestBytes.length; index++) {
hashString.append(Integer.toString((
messageDigestBytes[index] & 0xff) + 0x100, 16).substring(1));
}
mNameDecryptKey.put(prefixString, hashString.toString());
}
private void readChecksumFile(String filePathString, String prefixString) throws IOException {
if (!Files.exists(Paths.get(filePathString))) {
return;
}
byte[] allBytes = Files.readAllBytes(Paths.get(filePathString));
String checksumDataBase64 = mEncoder.encodeToString(allBytes);
mNameEncryptKey.put(prefixString, checksumDataBase64);
}
/**
* Parses a file to search for the key and put it into the pending compute queue
*/
@VisibleForTesting
public void parseConfigFile(String filePathString)
throws IOException, InterruptedException {
String prefixString = null;
String dataString = null;
String name = null;
String key = null;
int index;
if (!Files.exists(Paths.get(filePathString))) {
return;
}
List<String> allLinesString = Files.readAllLines(Paths.get(filePathString));
for (String line : allLinesString) {
if (line.startsWith("[")) {
name = line.replace("[", "").replace("]", "");
continue;
}
index = line.indexOf(" = ");
if (index < 0) {
continue;
}
key = line.substring(0, index);
if (!mEncryptKeyNameList.contains(key)) {
continue;
}
if (name == null) {
continue;
}
prefixString = name + "-" + key;
dataString = line.substring(index + 3);
if (dataString.length() == 0) {
continue;
}
mNameDecryptKey.put(prefixString, dataString);
mPendingEncryptKey.put(prefixString);
}
}
/**
* Load encryption file and push into mNameEncryptKey and pendingDecryptKey.
*/
@VisibleForTesting
public void loadEncryptionFile(String filePathString, boolean doDecrypt)
throws InterruptedException {
try {
if (!Files.exists(Paths.get(filePathString))) {
return;
}
List<String> allLinesString = Files.readAllLines(Paths.get(filePathString));
for (String line : allLinesString) {
int index = line.lastIndexOf("-");
if (index < 0) {
continue;
}
String prefixString = line.substring(0, index);
String encryptedString = line.substring(index + 1);
mNameEncryptKey.put(prefixString, encryptedString);
if (doDecrypt) {
mPendingDecryptKey.put(prefixString);
}
}
} catch (IOException e) {
throw new RuntimeException("read encryption file all line fail");
}
}
// will retry TRY_MAX times.
private String tryCompute(String sourceData, boolean doEncrypt) {
int counter = 0;
String targetData = null;
if (sourceData == null) {
return null;
}
while (targetData == null && counter < TRY_MAX) {
if (doEncrypt) {
targetData = encrypt(sourceData);
} else {
targetData = decrypt(sourceData);
}
counter++;
}
return targetData;
}
/**
* Encrypt the provided data blob.
*
* @param data String to be encrypted.
* @return String as base64.
*/
private @Nullable String encrypt(String data) {
BluetoothKeystoreProto.EncryptedData protobuf;
byte[] outputBytes;
String outputBase64 = null;
try {
if (data == null) {
errorLog("encrypt: data is null");
return outputBase64;
}
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecretKey secretKeyReference = getOrCreateSecretKey();
if (secretKeyReference != null) {
cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference);
protobuf = BluetoothKeystoreProto.EncryptedData.newBuilder()
.setEncryptedData(ByteString.copyFrom(cipher.doFinal(data.getBytes())))
.setInitVector(ByteString.copyFrom(cipher.getIV())).build();
outputBytes = protobuf.toByteArray();
if (outputBytes == null) {
errorLog("encrypt: Failed to serialize EncryptedData protobuf.");
return outputBase64;
}
outputBase64 = mEncoder.encodeToString(outputBytes);
} else {
errorLog("encrypt: secretKeyReference is null.");
}
} catch (NoSuchAlgorithmException e) {
reportKeystoreException(e, "encrypt could not find the algorithm: " + CIPHER_ALGORITHM);
} catch (NoSuchPaddingException e) {
reportKeystoreException(e, "encrypt had a padding exception");
} catch (InvalidKeyException e) {
reportKeystoreException(e, "encrypt received an invalid key");
} catch (BadPaddingException e) {
reportKeystoreException(e, "encrypt had a padding problem");
} catch (IllegalBlockSizeException e) {
reportKeystoreException(e, "encrypt had an illegal block size");
}
return outputBase64;
}
/**
* Decrypt the original data blob from the provided {@link EncryptedData}.
*
* @param data String as base64 to be decrypted.
* @return String.
*/
private @Nullable String decrypt(String encryptedDataBase64) {
BluetoothKeystoreProto.EncryptedData protobuf;
byte[] encryptedDataBytes;
byte[] decryptedDataBytes;
String output = null;
try {
if (encryptedDataBase64 == null) {
errorLog("decrypt: encryptedDataBase64 is null");
return output;
}
encryptedDataBytes = mDecoder.decode(encryptedDataBase64);
protobuf = BluetoothKeystoreProto.EncryptedData.parser().parseFrom(encryptedDataBytes);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
GCMParameterSpec spec =
new GCMParameterSpec(GCM_TAG_LENGTH, protobuf.getInitVector().toByteArray());
SecretKey secretKeyReference = getOrCreateSecretKey();
if (secretKeyReference != null) {
cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec);
decryptedDataBytes = cipher.doFinal(protobuf.getEncryptedData().toByteArray());
output = new String(decryptedDataBytes);
} else {
errorLog("decrypt: secretKeyReference is null.");
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
reportBluetoothKeystoreException(e, "decrypt: Failed to parse EncryptedData protobuf.");
} catch (NoSuchAlgorithmException e) {
reportKeystoreException(e,
"decrypt could not find cipher algorithm " + CIPHER_ALGORITHM);
} catch (NoSuchPaddingException e) {
reportKeystoreException(e, "decrypt could not find padding algorithm");
} catch (IllegalBlockSizeException e) {
reportKeystoreException(e, "decrypt had a illegal block size");
} catch (BadPaddingException e) {
reportKeystoreException(e, "decrypt had bad padding");
} catch (InvalidKeyException e) {
reportKeystoreException(e, "decrypt had an invalid key");
} catch (InvalidAlgorithmParameterException e) {
reportKeystoreException(e, "decrypt had an invalid algorithm parameter");
}
return output;
}
private KeyStore getKeyStore() {
KeyStore keyStore = null;
int counter = 0;
while ((counter <= TRY_MAX) && (keyStore == null)) {
try {
keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.BLUETOOTH_UID);
} catch (NoSuchProviderException e) {
reportKeystoreException(e, "cannot find crypto provider");
} catch (KeyStoreException e) {
reportKeystoreException(e, "cannot find the keystore");
}
counter++;
}
return keyStore;
}
// The getOrGenerate semantic on keystore is not thread safe, need to synchronized it.
private synchronized SecretKey getOrCreateSecretKey() {
SecretKey secretKey = null;
try {
KeyStore keyStore = getKeyStore();
if (keyStore.containsAlias(KEYALIAS)) { // The key exists in key store. Get the key.
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore
.getEntry(KEYALIAS, null);
if (secretKeyEntry != null) {
secretKey = secretKeyEntry.getSecretKey();
} else {
errorLog("decrypt: secretKeyEntry is null.");
}
} else {
// The key does not exist in key store. Create the key and store it.
KeyGenerator keyGenerator = KeyGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE);
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEYALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(KEY_LENGTH)
.setUid(Process.BLUETOOTH_UID)
.build();
keyGenerator.init(keyGenParameterSpec);
secretKey = keyGenerator.generateKey();
}
} catch (KeyStoreException e) {
reportKeystoreException(e, "cannot find the keystore: " + KEY_STORE);
} catch (InvalidAlgorithmParameterException e) {
reportKeystoreException(e, "getOrCreateSecretKey had an invalid algorithm parameter");
} catch (NoSuchAlgorithmException e) {
reportKeystoreException(e, "getOrCreateSecretKey cannot find algorithm");
} catch (NoSuchProviderException e) {
reportKeystoreException(e, "getOrCreateSecretKey cannot find crypto provider");
} catch (UnrecoverableEntryException e) {
reportKeystoreException(e,
"getOrCreateSecretKey had an unrecoverable entry exception.");
} catch (ProviderException e) {
reportKeystoreException(e, "getOrCreateSecretKey had a provider exception.");
}
return secretKey;
}
private static void reportKeystoreException(Exception exception, String error) {
Log.wtf(TAG, "A keystore error was encountered: " + error, exception);
}
private static void reportBluetoothKeystoreException(Exception exception, String error) {
Log.wtf(TAG, "A bluetoothkey store error was encountered: " + error, exception);
}
private static void infoLog(String msg) {
if (DBG) {
Log.i(TAG, msg);
}
}
private static void debugLog(String msg) {
Log.d(TAG, msg);
}
private static void errorLog(String msg) {
Log.e(TAG, msg);
}
/**
* A thread that decrypt data if the queue has new decrypt task.
*/
private class ComputeDataThread extends Thread {
private Map<String, String> mSourceDataMap;
private Map<String, String> mTargetDataMap;
private BlockingQueue<String> mSourceQueue;
private boolean mDoEncrypt;
private boolean mWaitQueueEmptyForStop;
ComputeDataThread(boolean doEncrypt) {
infoLog("ComputeDataThread: create, doEncrypt: " + doEncrypt);
mWaitQueueEmptyForStop = false;
mDoEncrypt = doEncrypt;
if (mDoEncrypt) {
mSourceDataMap = mNameDecryptKey;
mTargetDataMap = mNameEncryptKey;
mSourceQueue = mPendingEncryptKey;
} else {
mSourceDataMap = mNameEncryptKey;
mTargetDataMap = mNameDecryptKey;
mSourceQueue = mPendingDecryptKey;
}
}
@Override
public void run() {
infoLog("ComputeDataThread: run, doEncrypt: " + mDoEncrypt);
String prefixString;
String sourceData;
String targetData;
while (!mSourceQueue.isEmpty() || !mWaitQueueEmptyForStop) {
try {
prefixString = mSourceQueue.take();
if (mSourceDataMap.containsKey(prefixString)) {
sourceData = mSourceDataMap.get(prefixString);
targetData = tryCompute(sourceData, mDoEncrypt);
if (targetData != null) {
mTargetDataMap.put(prefixString, targetData);
} else {
errorLog("Computing of Data failed with prefixString: " + prefixString
+ ", doEncrypt: " + mDoEncrypt);
}
}
} catch (InterruptedException e) {
infoLog("Interrupted while operating.");
}
}
infoLog("ComputeDataThread: Stop, doEncrypt: " + mDoEncrypt);
}
public void setWaitQueueEmptyForStop() {
mWaitQueueEmptyForStop = true;
if (mPendingEncryptKey.isEmpty()) {
interrupt();
}
}
}
}