From 5f67e5ff1c7eb4255e5faa09bf1f31e3d151deee Mon Sep 17 00:00:00 2001 From: Martin Brabham Date: Fri, 1 Feb 2019 13:42:58 -0800 Subject: [PATCH] [DO NOT MERGE] Implement key attestation using AndroidKeystore. Store SHA256 hash of the config file in an encrypted file that has been encrypted using the Android Keystore API. Bug: b/117993149 Test: Manual Change-Id: I26de9ea05f515d6643a83d11628490fb49e10743 Merged-In: I26de9ea05f515d6643a83d11628490fb49e10743 --- btif/Android.bp | 16 ++++ btif/include/btif_keystore.h | 39 ++++++++++ btif/src/btif_config.cc | 106 ++++++++++++++++++++++++- btif/src/btif_keystore.cc | 146 +++++++++++++++++++++++++++++++++++ main/Android.bp | 8 ++ test/suite/adapter/bluetooth_test.cc | 5 ++ 6 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 btif/include/btif_keystore.h create mode 100644 btif/src/btif_keystore.cc diff --git a/btif/Android.bp b/btif/Android.bp index 3b4e57464..e0037254a 100644 --- a/btif/Android.bp +++ b/btif/Android.bp @@ -24,6 +24,8 @@ btifCommonIncludes = [ "system/bt/utils/include", "system/bt/include", "system/libhwbinder/include", + "system/security/keystore/include", + "hardware/interfaces/keymaster/4.0/support/include", ] // libbtif static library for target @@ -71,6 +73,8 @@ cc_library_static { "src/btif_hf_client.cc", "src/btif_hh.cc", "src/btif_hd.cc", + "src/btif_hl.cc", + "src/btif_keystore.cc", "src/btif_mce.cc", "src/btif_pan.cc", "src/btif_profile_queue.cc", @@ -103,6 +107,12 @@ cc_library_static { "libhwbinder", "libutils", "libcrypto", + "android.hardware.keymaster@4.0", + "android.hardware.keymaster@3.0", + "libkeymaster4support", + "libkeystore_aidl", + "libkeystore_binder", + "libkeystore_parcelables", ], whole_static_libs: [ "avrcp-target-service", @@ -138,6 +148,12 @@ cc_test { "libprocessgroup", "libutils", "libcrypto", + "android.hardware.keymaster@4.0", + "android.hardware.keymaster@3.0", + "libkeymaster4support", + "libkeystore_aidl", + "libkeystore_binder", + "libkeystore_parcelables", ], static_libs: [ "libbt-bta", diff --git a/btif/include/btif_keystore.h b/btif/include/btif_keystore.h new file mode 100644 index 000000000..1e5c2eab8 --- /dev/null +++ b/btif/include/btif_keystore.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * + * Copyright 2019 Google, Inc. + * + * 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. + * + ******************************************************************************/ + +#include +#include + +#include "osi/include/alarm.h" +#include "osi/include/allocator.h" +#include "osi/include/compat.h" +#include "osi/include/config.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" + +using namespace keystore; + +class BtifKeystore { + public: + BtifKeystore(); + ~BtifKeystore(); + int Encrypt(const std::string& hash, const std::string& output_filename, + int32_t flags); + std::string Decrypt(const std::string& input_filename); +}; diff --git a/btif/src/btif_config.cc b/btif/src/btif_config.cc index c017c0f47..c20b78382 100644 --- a/btif/src/btif_config.cc +++ b/btif/src/btif_config.cc @@ -23,10 +23,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -36,6 +38,7 @@ #include "btif_api.h" #include "btif_common.h" #include "btif_config_transcode.h" +#include "btif_keystore.h" #include "btif_util.h" #include "common/address_obfuscator.h" #include "osi/include/alarm.h" @@ -66,6 +69,8 @@ static const char* CONFIG_LEGACY_FILE_PATH = "bt_config.xml"; #else // !defined(OS_GENERIC) static const char* CONFIG_FILE_PATH = "/data/misc/bluedroid/bt_config.conf"; static const char* CONFIG_BACKUP_PATH = "/data/misc/bluedroid/bt_config.bak"; +static const char* CONFIG_FILE_CHECKSUM_PATH = "/data/misc/bluedroid/bt_config.conf.encrypted-checksum"; +static const char* CONFIG_BACKUP_CHECKSUM_PATH = "/data/misc/bluedroid/bt_config.bak.encrypted-checksum"; static const char* CONFIG_LEGACY_FILE_PATH = "/data/misc/bluedroid/bt_config.xml"; #endif // defined(OS_GENERIC) @@ -77,7 +82,14 @@ static bool is_factory_reset(void); static void delete_config_files(void); static void btif_config_remove_unpaired(config_t* config); static void btif_config_remove_restricted(config_t* config); -static std::unique_ptr btif_config_open(const char* filename); +static std::unique_ptr btif_config_open(const char* filename, const char* checksum_filename); + +// Key attestation +static std::string hash_file(const char* filename); +static std::string read_checksum_file(const char* filename); +static void write_checksum_file(const char* filename, const std::string hash); +static bool verify_hash(const std::string current_hash, + const std::string stored_hash); static enum ConfigSource { NOT_LOADED, @@ -157,6 +169,7 @@ static void read_or_set_metrics_salt() { static std::recursive_mutex config_lock; // protects operations on |config|. static std::unique_ptr config; static alarm_t* config_timer; +static BtifKeystore btifKeystore; // Module lifecycle functions @@ -167,12 +180,13 @@ static future_t* init(void) { std::string file_source; - config = btif_config_open(CONFIG_FILE_PATH); + config = btif_config_open(CONFIG_FILE_PATH, CONFIG_FILE_CHECKSUM_PATH); btif_config_source = ORIGINAL; if (!config) { LOG_WARN(LOG_TAG, "%s unable to load config file: %s; using backup.", __func__, CONFIG_FILE_PATH); - config = btif_config_open(CONFIG_BACKUP_PATH); + remove(CONFIG_FILE_CHECKSUM_PATH); + config = btif_config_open(CONFIG_BACKUP_PATH, CONFIG_BACKUP_CHECKSUM_PATH); btif_config_source = BACKUP; file_source = "Backup"; } @@ -180,6 +194,7 @@ static future_t* init(void) { LOG_WARN(LOG_TAG, "%s unable to load backup; attempting to transcode legacy file.", __func__); + remove(CONFIG_BACKUP_CHECKSUM_PATH); config = btif_config_transcode(CONFIG_LEGACY_FILE_PATH); btif_config_source = LEGACY; file_source = "Legacy"; @@ -239,7 +254,25 @@ error: return future_new_immediate(FUTURE_FAIL); } -static std::unique_ptr btif_config_open(const char* filename) { +static std::unique_ptr btif_config_open(const char* filename, const char* checksum_filename) { + // START KEY ATTESTATION + // Get hash of current file + std::string current_hash = hash_file(filename); + // Get stored hash + std::string stored_hash = read_checksum_file(checksum_filename); + if (stored_hash.empty()) { + LOG(ERROR) << __func__ << ": stored_hash="; + if (!current_hash.empty()) { + write_checksum_file(checksum_filename, current_hash); + stored_hash = read_checksum_file(checksum_filename); + } + } + // Compare hashes + if (!verify_hash(current_hash, stored_hash)) { + return nullptr; + } + // END KEY ATTESTATION + std::unique_ptr config = config_new(filename); if (!config) return nullptr; @@ -465,6 +498,13 @@ bool btif_config_clear(void) { bool ret = config_save(*config, CONFIG_FILE_PATH); btif_config_source = RESET; + + // Save encrypted hash + std::string current_hash = hash_file(CONFIG_FILE_PATH); + if (!current_hash.empty()) { + write_checksum_file(CONFIG_FILE_CHECKSUM_PATH, current_hash); + } + return ret; } @@ -482,9 +522,15 @@ static void btif_config_write(UNUSED_ATTR uint16_t event, std::unique_lock lock(config_lock); rename(CONFIG_FILE_PATH, CONFIG_BACKUP_PATH); + rename(CONFIG_FILE_CHECKSUM_PATH, CONFIG_BACKUP_CHECKSUM_PATH); std::unique_ptr config_paired = config_new_clone(*config); btif_config_remove_unpaired(config_paired.get()); config_save(*config_paired, CONFIG_FILE_PATH); + // Save hash + std::string current_hash = hash_file(CONFIG_FILE_PATH); + if (!current_hash.empty()) { + write_checksum_file(CONFIG_FILE_CHECKSUM_PATH, current_hash); + } } static void btif_config_remove_unpaired(config_t* conf) { @@ -576,5 +622,57 @@ static bool is_factory_reset(void) { static void delete_config_files(void) { remove(CONFIG_FILE_PATH); remove(CONFIG_BACKUP_PATH); + remove(CONFIG_FILE_CHECKSUM_PATH); + remove(CONFIG_BACKUP_CHECKSUM_PATH); osi_property_set("persist.bluetooth.factoryreset", "false"); } + +static std::string hash_file(const char* filename) { + FILE* fp = fopen(filename, "rb"); + if (!fp) { + LOG(ERROR) << __func__ << ": unable to open config file: '" << filename + << "': " << strerror(errno); + return ""; + } + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + const int bufSize = 400 * 10; // initial file is ~400B + std::byte* buffer = (std::byte*) osi_calloc(bufSize); + int bytesRead = 0; + if (!buffer) return ""; + while ((bytesRead = fread(buffer, 1, bufSize, fp))) { + SHA256_Update(&sha256, buffer, bytesRead); + } + SHA256_Final(hash, &sha256); + std::stringstream ss; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + } + fclose(fp); + osi_free(buffer); + return ss.str(); +} + +static std::string read_checksum_file(const char* checksum_filename) { + // Ensure file exists + FILE* fp = fopen(checksum_filename, "rb"); + if (!fp) { + return ""; + } else { + fclose(fp); + } + std::string output = btifKeystore.Decrypt(checksum_filename); + return output; +} + +static void write_checksum_file(const char* checksum_filename, std::string hash) { + int result = btifKeystore.Encrypt(hash, checksum_filename, 0); + if (result != 0) { + LOG(ERROR) << "Failed writing checksum!"; + } +} + +static bool verify_hash(std::string current_hash, std::string stored_hash) { + return current_hash.compare(stored_hash) == 0; +} diff --git a/btif/src/btif_keystore.cc b/btif/src/btif_keystore.cc new file mode 100644 index 000000000..0805d2147 --- /dev/null +++ b/btif/src/btif_keystore.cc @@ -0,0 +1,146 @@ +/****************************************************************************** + * + * Copyright 2019 Google, Inc. + * + * 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. + * + ******************************************************************************/ + +#define LOG_TAG "bt_btif_keystore" + +#include "btif_keystore.h" +#include "osi/include/properties.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace keystore; + +static std::unique_ptr CreateKeystoreInstance(void); +static void WriteFile(const std::string& filename, const std::string& content); +static std::string ReadFile(const std::string& filename); +static int GenerateKey(const std::string& name, int32_t flags, bool auth_bound); +static bool DoesKeyExist(const std::string& name); + +const std::string FILE_SUFFIX = ".encrypted-checksum"; +const std::string CIPHER_ALGORITHM = "AES/GCM/NoPadding"; +const std::string DIGEST_ALGORITHM = "SHA-256"; +const std::string KEY_STORE = "AndroidKeystore"; + +std::unique_ptr keystoreClient; + +BtifKeystore::BtifKeystore() { keystoreClient = CreateKeystoreInstance(); } + +BtifKeystore::~BtifKeystore() { + // Using a smart pointer, does it delete itself? + // delete keystoreClient; +} + +int BtifKeystore::Encrypt(const std::string& hash, + const std::string& output_filename, int32_t flags) { + std::string output; + if (!DoesKeyExist(KEY_STORE)) { + GenerateKey(KEY_STORE, 0, false); + } + char is_unittest[PROPERTY_VALUE_MAX] = {0}; + osi_property_get("debug.bluetooth.unittest", is_unittest, "false"); + if (strcmp(is_unittest, "false") == 0) { + if (!keystoreClient->encryptWithAuthentication(KEY_STORE, hash, flags, + &output)) { + LOG(ERROR) << "EncryptWithAuthentication failed.\n"; + return 1; + } + } + WriteFile(output_filename, output); + return 0; +} + +std::string BtifKeystore::Decrypt(const std::string& input_filename) { + std::string input = ReadFile(input_filename); + std::string output; + + char is_unittest[PROPERTY_VALUE_MAX] = {0}; + osi_property_get("debug.bluetooth.unittest", is_unittest, "false"); + if (strcmp(is_unittest, "false") == 0) { + if (!keystoreClient->decryptWithAuthentication(KEY_STORE, input, &output)) { + LOG(ERROR) << "DecryptWithAuthentication failed.\n"; + } + } + return output; +} + +static std::string ReadFile(const std::string& filename) { + std::string content; + struct stat buffer; + if (stat(filename.c_str(), &buffer) == 0) { + base::FilePath path(filename); + if (!base::ReadFileToString(path, &content)) { + LOG(ERROR) << "ReadFile failed.\n" << filename.c_str(); + } + } + return content; +} + +static void WriteFile(const std::string& filename, const std::string& content) { + base::FilePath path(filename); + int size = content.size(); + if (base::WriteFile(path, content.data(), size) != size) { + LOG(ERROR) << "WriteFile failed.\n" << filename.c_str(); + } +} + +// Note: auth_bound keys created with this tool will not be usable. +static int GenerateKey(const std::string& name, int32_t flags, + bool auth_bound) { + AuthorizationSetBuilder params; + params.RsaSigningKey(2048, 65537) + .Digest(Digest::SHA_2_224) + .Digest(Digest::SHA_2_256) + .Digest(Digest::SHA_2_384) + .Digest(Digest::SHA_2_512) + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .Padding(PaddingMode::RSA_PSS); + if (auth_bound) { + // Gatekeeper normally generates the secure user id. + // Using zero allows the key to be created, but it will not be usuable. + params.Authorization(TAG_USER_SECURE_ID, 0); + } else { + params.Authorization(TAG_NO_AUTH_REQUIRED); + } + AuthorizationSet hardware_enforced_characteristics; + AuthorizationSet software_enforced_characteristics; + + char is_unittest[PROPERTY_VALUE_MAX] = {0}; + osi_property_get("debug.bluetooth.unittest", is_unittest, "false"); + if (strcmp(is_unittest, "false") != 0) { + return -1; + } + auto result = keystoreClient->generateKey(name, params, flags, + &hardware_enforced_characteristics, + &software_enforced_characteristics); + return result.getErrorCode(); +} + +static bool DoesKeyExist(const std::string& name) { + return keystoreClient->doesKeyExist(name) ? true : false; +} + +static std::unique_ptr CreateKeystoreInstance(void) { + return std::unique_ptr( + static_cast(new KeystoreClientImpl)); +} diff --git a/main/Android.bp b/main/Android.bp index 942ee9c5d..8b3256215 100644 --- a/main/Android.bp +++ b/main/Android.bp @@ -35,6 +35,8 @@ cc_library_shared { "system/bt/embdrv/sbc/encoder/include", "system/bt/embdrv/sbc/decoder/include", "system/bt/utils/include", + "system/security/keystore/include", + "hardware/interfaces/keymaster/4.0/support/include", ], logtags: ["../EventLogTags.logtags"], shared_libs: [ @@ -55,6 +57,12 @@ cc_library_shared { "libtinyxml2", "libz", "libcrypto", + "android.hardware.keymaster@4.0", + "android.hardware.keymaster@3.0", + "libkeymaster4support", + "libkeystore_aidl", + "libkeystore_binder", + "libkeystore_parcelables", ], static_libs: [ "libbt-sbc-decoder", diff --git a/test/suite/adapter/bluetooth_test.cc b/test/suite/adapter/bluetooth_test.cc index 02132f1d0..012592f1b 100644 --- a/test/suite/adapter/bluetooth_test.cc +++ b/test/suite/adapter/bluetooth_test.cc @@ -19,6 +19,7 @@ #include "adapter/bluetooth_test.h" #include #include "btcore/include/property.h" +#include "osi/include/properties.h" namespace { @@ -46,6 +47,8 @@ void BluetoothTest::SetUp() { adapter_state_changed_callback_sem_ = semaphore_new(0); discovery_state_changed_callback_sem_ = semaphore_new(0); + osi_property_set("debug.bluetooth.unittest", "true"); + bluetooth::hal::BluetoothInterface::Initialize(); ASSERT_TRUE(bluetooth::hal::BluetoothInterface::IsInitialized()); auto bt_hal_interface = bluetooth::hal::BluetoothInterface::Get(); @@ -64,6 +67,8 @@ void BluetoothTest::TearDown() { bt_hal_interface->RemoveObserver(this); bt_hal_interface->CleanUp(); ASSERT_FALSE(bt_hal_interface->IsInitialized()); + + osi_property_set("debug.bluetooth.unittest", "false"); } void BluetoothTest::ClearSemaphore(semaphore_t* sem) { -- 2.11.0