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