From 1f5ff4332f4c310ad4e34047a8159cb271a6d064 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Fri, 11 Aug 2017 16:34:10 +0200 Subject: [PATCH] le_device_db_tlv: le_device_db.h implementation on top of btstack_tlv.h interface --- src/ble/le_device_db.h | 6 +- src/ble/le_device_db_tlv.c | 439 +++++++++++++++++++++++++++++++++++ src/ble/le_device_db_tlv.h | 64 +++++ test/flash_tlv/.gitignore | 2 + test/flash_tlv/Makefile | 21 +- test/flash_tlv/tlv_le_test.c | 123 ++++++++++ 6 files changed, 642 insertions(+), 13 deletions(-) create mode 100644 src/ble/le_device_db_tlv.c create mode 100644 src/ble/le_device_db_tlv.h create mode 100644 test/flash_tlv/tlv_le_test.c diff --git a/src/ble/le_device_db.h b/src/ble/le_device_db.h index 5e8668434..209524b23 100644 --- a/src/ble/le_device_db.h +++ b/src/ble/le_device_db.h @@ -47,11 +47,11 @@ extern "C" { /** - LE Device DB for pure LE Peripherals is only required for signed writes + Note: LE Device DB for pure LE Peripherals is not required if only LE Legacy Pairing without signed writes is used - Per bonded device, it can store + Per bonded device: - it stores the Identity Resolving Key (IRK) and its address to resolve private addresses - - it stores the LTK + EDIV, RAND. EDIV + RAND allow a LE Perihperal to reconstruct the LTK + - it stores the LTK + EDIV, RAND. EDIV + RAND allows a LE Peripheral to reconstruct the LTK - it stores the Connection Signature Resolving Key (CSRK) and the last used counter. The CSRK is used to generate the signatur on the remote device and is needed to verify the signature itself The Counter is necessary to prevent reply attacks diff --git a/src/ble/le_device_db_tlv.c b/src/ble/le_device_db_tlv.c new file mode 100644 index 000000000..a9ae841ee --- /dev/null +++ b/src/ble/le_device_db_tlv.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define __BTSTACK_FILE__ "le_device_db_tlv.c" + +#include "ble/le_device_db.h" +#include "ble/le_device_db_tlv.h" + +#include "ble/core.h" + +#include +#include +#include "btstack_debug.h" + +// LE Device DB Implementation storing entries in btstack_tlv + +// Local cache is used to keep track of deleted entries in TLV + +#define INVALID_ENTRY_ADDR_TYPE 0xff + +// Single stored entry +typedef struct le_device_db_entry_t { + + // Identification + int addr_type; + bd_addr_t addr; + sm_key_t irk; + + // Stored pairing information allows to re-establish an enncrypted connection + // with a peripheral that doesn't have any persistent memory + sm_key_t ltk; + uint16_t ediv; + uint8_t rand[8]; + uint8_t key_size; + uint8_t authenticated; + uint8_t authorized; + +#ifdef ENABLE_LE_SIGNED_WRITE + // Signed Writes by remote + sm_key_t remote_csrk; + uint32_t remote_counter; + + // Signed Writes by us + sm_key_t local_csrk; + uint32_t local_counter; +#endif + +} le_device_db_entry_t; + + +#ifndef MAX_NR_LE_DEVICE_DB_ENTRIES +#error "MAX_NR_LE_DEVICE_DB_ENTRIES not defined, please define in btstack_config.h" +#endif + +#if MAX_NR_LE_DEVICE_DB_ENTRIES == 0 +#error "MAX_NR_LE_DEVICE_DB_ENTRIES must not be 0, please update in btstack_config.h" +#endif + +static uint8_t entry_map[MAX_NR_LE_DEVICE_DB_ENTRIES]; +static uint32_t num_valid_entries; + +static const btstack_tlv_t * le_device_db_tlv_btstack_tlv_impl; +static void * le_device_db_tlv_btstack_tlv_context; + +static const char tag_0 = 'B'; +static const char tag_1 = 'T'; +static const char tag_2 = 'D'; + +static uint32_t le_device_db_tlv_tag_for_index(uint8_t index){ + return (tag_0 << 24) | (tag_1 << 16) | (tag_2 << 8) | index; +} + +// @returns success +// @param index = entry_pos +static int le_device_db_tlv_fetch(int index, le_device_db_entry_t * entry){ + if (index < 0 || index >= MAX_NR_LE_DEVICE_DB_ENTRIES){ + log_error("le_device_db_tlv_fetch called with invalid index %d", index); + return 0; + } + uint32_t tag = le_device_db_tlv_tag_for_index(index); + int size = le_device_db_tlv_btstack_tlv_impl->get_tag(le_device_db_tlv_btstack_tlv_context, tag, (uint8_t*) entry, sizeof(le_device_db_entry_t)); + return size != 0; +} + +// @returns success +// @param index = entry_pos +static int le_device_db_tlv_store(int index, le_device_db_entry_t * entry){ + if (index < 0 || index >= MAX_NR_LE_DEVICE_DB_ENTRIES){ + log_error("le_device_db_tlv_store called with invalid index %d", index); + return 0; + } + uint32_t tag = le_device_db_tlv_tag_for_index(index); + le_device_db_tlv_btstack_tlv_impl->store_tag(le_device_db_tlv_btstack_tlv_context, tag, (uint8_t*) entry, sizeof(le_device_db_entry_t)); + return 1; +} + +// @param index = entry_pos +static int le_device_db_tlv_delete(int index){ + if (index < 0 || index >= MAX_NR_LE_DEVICE_DB_ENTRIES){ + log_error("le_device_db_tlv_delete called with invalid index %d", index); + return 0; + } + uint32_t tag = le_device_db_tlv_tag_for_index(index); + le_device_db_tlv_btstack_tlv_impl->delete_tag(le_device_db_tlv_btstack_tlv_context, tag); + return 1; +} + +static int le_device_db_tlv_fetch_mapped(int index, le_device_db_entry_t * entry){ + if (index < 0 || index >= num_valid_entries){ + log_error("le_device_db_tlv_fetch_mapped called with invalid index %d", index); + return 0; + } + return le_device_db_tlv_fetch(entry_map[index], entry); +} + +static int le_device_db_tlv_store_mapped(int index, le_device_db_entry_t * entry){ + if (index < 0 || index >= num_valid_entries){ + log_error("le_device_tlv_store_mapped called with invalid index %d", index); + return 0; + } + return le_device_db_tlv_store_mapped(entry_map[index], entry); +} + + +void le_device_db_init(void){ + int i; + num_valid_entries = 0; + memset(entry_map, 0, sizeof(entry_map)); + for (i=0;i index; i--){ + entry_map[i] = entry_map[i-1]; + } + + // set in entry_mape + entry_map[index] = new_index; + + // store entry at entry_pos = index + le_device_db_entry_t entry; + log_info("LE Device DB adding type %u - %s", addr_type, bd_addr_to_str(addr)); + log_info_key("irk", irk); + + entry.addr_type = addr_type; + memcpy(entry.addr, addr, 6); + memcpy(entry.irk, irk, 16); +#ifdef ENABLE_LE_SIGNED_WRITE + entry.remote_counter = 0; +#endif + + // store + le_device_db_tlv_store(index, &entry); + + // keep track + num_valid_entries++; + + return index; +} + + +// get device information: addr type and address +void le_device_db_info(int index, int * addr_type, bd_addr_t addr, sm_key_t irk){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + if (addr_type) *addr_type = entry.addr_type; + if (addr) memcpy(addr, entry.addr, 6); + if (irk) memcpy(irk, entry.irk, 16); +} + +void le_device_db_encryption_set(int index, uint16_t ediv, uint8_t rand[8], sm_key_t ltk, int key_size, int authenticated, int authorized){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + // update + log_info("LE Device DB set encryption for %u, ediv x%04x, key size %u, authenticated %u, authorized %u", + index, ediv, key_size, authenticated, authorized); + entry.ediv = ediv; + if (rand) memcpy(entry.rand, rand, 8); + if (ltk) memcpy(entry.ltk, ltk, 16); + entry.key_size = key_size; + entry.authenticated = authenticated; + entry.authorized = authorized; + + // store + le_device_db_tlv_store_mapped(index, &entry); +} + +void le_device_db_encryption_get(int index, uint16_t * ediv, uint8_t rand[8], sm_key_t ltk, int * key_size, int * authenticated, int * authorized){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + // update user fields + log_info("LE Device DB encryption for %u, ediv x%04x, keysize %u, authenticated %u, authorized %u", + index, entry.ediv, entry.key_size, entry.authenticated, entry.authorized); + if (ediv) *ediv = entry.ediv; + if (rand) memcpy(rand, entry.rand, 8); + if (ltk) memcpy(ltk, entry.ltk, 16); + if (key_size) *key_size = entry.key_size; + if (authenticated) *authenticated = entry.authenticated; + if (authorized) *authorized = entry.authorized; +} + +#ifdef ENABLE_LE_SIGNED_WRITE + +// get signature key +void le_device_db_remote_csrk_get(int index, sm_key_t csrk){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + if (csrk) memcpy(csrk, entry.remote_csrk, 16); +} + +void le_device_db_remote_csrk_set(int index, sm_key_t csrk){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + if (!csrk) return; + + // update + memcpy(entry.remote_csrk, csrk, 16); + + // store + le_device_db_tlv_store_mapped(index, &entry); +} + +void le_device_db_local_csrk_get(int index, sm_key_t csrk){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + if (!csrk) return; + + // fill + memcpy(csrk, entry.local_csrk, 16); +} + +void le_device_db_local_csrk_set(int index, sm_key_t csrk){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + if (!csrk) return; + + // update + memcpy(entry.local_csrk, csrk, 16); + + // store + le_device_db_tlv_store_mapped(index, &entry); +} + +// query last used/seen signing counter +uint32_t le_device_db_remote_counter_get(int index){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return 0; + + return entry.remote_counter; +} + +// update signing counter +void le_device_db_remote_counter_set(int index, uint32_t counter){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + entry.remote_counter = counter; + + // store + le_device_db_tlv_store_mapped(index, &entry); +} + +// query last used/seen signing counter +uint32_t le_device_db_local_counter_get(int index){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return 0; + + return entry.local_counter; +} + +// update signing counter +void le_device_db_local_counter_set(int index, uint32_t counter){ + + // fetch entry + le_device_db_entry_t entry; + int ok = le_device_db_tlv_fetch_mapped(index, &entry); + if (!ok) return; + + // update + entry.local_counter = counter; + + // store + le_device_db_tlv_store_mapped(index, &entry); +} + +#endif + +void le_device_db_dump(void){ + log_info("LE Device DB dump, devices: %d", le_device_db_count()); + int i; + for (i=0;ierase(&hal_flash_sector_context, 0); + hal_flash_sector_impl->erase(&hal_flash_sector_context, 1); + // btstack_tlv + btstack_tlv_impl = btstack_tlv_flash_sector_init_instance(&btstack_tlv_context, hal_flash_sector_impl, &hal_flash_sector_context); + // le_device_db_tlv + le_device_db_tlv_configure(btstack_tlv_impl, &btstack_tlv_context); + le_device_db_init(); + + bd_addr_t addr_1 = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa }; + bd_addr_t addr_2 = { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb }; + bd_addr_t addr_3 = { 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc }; + bd_addr_copy(addr_aa, addr_1); + bd_addr_copy(addr_bb, addr_2); + bd_addr_copy(addr_cc, addr_3); + memset(sm_key_aa, 0xaa, 16); + memset(sm_key_bb, 0xbb, 16); + memset(sm_key_cc, 0xcc, 16); + } +}; + +TEST(LE_DEVICE_DB, Empty){ + CHECK_EQUAL(0, le_device_db_count()); +} + +TEST(LE_DEVICE_DB, AddOne){ + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_aa, sm_key_aa); + CHECK_EQUAL(1, le_device_db_count()); +} + +TEST(LE_DEVICE_DB, RetrieveOne){ + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_aa, sm_key_aa); + CHECK_EQUAL(1, le_device_db_count()); + bd_addr_t addr; + sm_key_t sm_key; + int addr_type; + le_device_db_info(0, &addr_type, addr, sm_key); + CHECK_EQUAL_ARRAY(sm_key_aa, sm_key, 16); + CHECK_EQUAL_ARRAY(addr_aa, addr, 6); +} + +TEST(LE_DEVICE_DB, AddOTwo){ + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_aa, sm_key_aa); + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_bb, sm_key_bb); + CHECK_EQUAL(2, le_device_db_count()); +} + +TEST(LE_DEVICE_DB, AddOTwoRemoveOne){ + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_aa, sm_key_aa); + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_bb, sm_key_bb); + le_device_db_remove(0); + CHECK_EQUAL(1, le_device_db_count()); + bd_addr_t addr; + sm_key_t sm_key; + int addr_type; + le_device_db_info(0, &addr_type, addr, sm_key); + CHECK_EQUAL_ARRAY(sm_key_bb, sm_key, 16); + CHECK_EQUAL_ARRAY(addr_bb, addr, 6); +} + +TEST(LE_DEVICE_DB, AddOTwoRemoveOneAddOne){ + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_aa, sm_key_aa); + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_bb, sm_key_bb); + le_device_db_remove(0); + le_device_db_add(BD_ADDR_TYPE_LE_PUBLIC, addr_cc, sm_key_cc); + CHECK_EQUAL(2, le_device_db_count()); + bd_addr_t addr; + sm_key_t sm_key; + int addr_type; + le_device_db_info(0, &addr_type, addr, sm_key); + CHECK_EQUAL_ARRAY(sm_key_cc, sm_key, 16); + CHECK_EQUAL_ARRAY(addr_cc, addr, 6); +} + + +int main (int argc, const char * argv[]){ + hci_dump_open("tlv_le_test.pklg", HCI_DUMP_PACKETLOGGER); + return CommandLineTestRunner::RunAllTests(argc, argv); +}