diff --git a/include/bluetooth/gap.h b/include/bluetooth/gap.h index 94b0316029d..86b478db8dc 100644 --- a/include/bluetooth/gap.h +++ b/include/bluetooth/gap.h @@ -56,6 +56,7 @@ extern "C" { #define BT_DATA_MESH_BEACON 0x2b /* Mesh Beacon */ #define BT_DATA_BIG_INFO 0x2c /* BIGInfo */ #define BT_DATA_BROADCAST_CODE 0x2d /* Broadcast Code */ +#define BT_DATA_CSIS_RSI 0x2e /* CSIS Random Set ID type */ #define BT_DATA_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */ diff --git a/include/bluetooth/uuid.h b/include/bluetooth/uuid.h index 38620751248..222d8df34ec 100644 --- a/include/bluetooth/uuid.h +++ b/include/bluetooth/uuid.h @@ -446,6 +446,15 @@ struct bt_uuid_128 { */ #define BT_UUID_VOCS \ BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL) +/** @def BT_UUID_CSIS_VAL + * @brief Coordinated Set Identification Service value + */ +#define BT_UUID_CSIS_VAL 0x1846 +/** @def BT_UUID_CSIS + * @brief Coordinated Set Identification Service + */ +#define BT_UUID_CSIS \ + BT_UUID_DECLARE_16(BT_UUID_CSIS_VAL) /** @def BT_UUID_MICS_VAL * @brief Microphone Input Control Service value */ @@ -1484,6 +1493,42 @@ struct bt_uuid_128 { */ #define BT_UUID_VOCS_DESCRIPTION \ BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL) +/** @def BT_UUID_CSIS_SET_SIRK_VAL + * @brief Set Identity Resolving Key value + */ +#define BT_UUID_CSIS_SET_SIRK_VAL 0x2B84 +/** @def BT_UUID_CSIS_SET_SIRK + * @brief Set Identity Resolving Key + */ +#define BT_UUID_CSIS_SET_SIRK \ + BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_SIRK_VAL) +/** @def BT_UUID_CSIS_SET_SIZE_VAL + * @brief Set size value + */ +#define BT_UUID_CSIS_SET_SIZE_VAL 0x2B85 +/** @def BT_UUID_CSIS_SET_SIZE + * @brief Set size + */ +#define BT_UUID_CSIS_SET_SIZE \ + BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_SIZE_VAL) +/** @def BT_UUID_CSIS_SET_LOCK_VAL + * @brief Set lock value + */ +#define BT_UUID_CSIS_SET_LOCK_VAL 0x2B86 +/** @def BT_UUID_CSIS_SET_LOCK + * @brief Set lock + */ +#define BT_UUID_CSIS_SET_LOCK \ + BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_LOCK_VAL) +/** @def BT_UUID_CSIS_RANK_VAL + * @brief Rank value + */ +#define BT_UUID_CSIS_RANK_VAL 0x2B87 +/** @def BT_UUID_CSIS_RANK + * @brief Rank + */ +#define BT_UUID_CSIS_RANK \ + BT_UUID_DECLARE_16(BT_UUID_CSIS_RANK_VAL) /** @def BT_UUID_CCID_VAL * @brief Content Control ID value */ diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index e58e520ce42..5e5ac0f56b4 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -25,3 +25,6 @@ zephyr_library_sources_ifdef(CONFIG_BT_MICS_CLIENT mics_client.c) zephyr_library_sources_ifdef(CONFIG_BT_CCID ccid.c) zephyr_library_link_libraries(subsys__bluetooth) + +zephyr_library_sources_ifdef(CONFIG_BT_CSIS csis.c) +zephyr_library_sources_ifdef(CONFIG_BT_CSIS csis_crypto.c) diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index 23b6eb079a8..649216c7c76 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -61,5 +61,6 @@ rsource "Kconfig.vocs" rsource "Kconfig.aics" rsource "Kconfig.vcs" rsource "Kconfig.mics" +rsource "Kconfig.csis" endif # BT_AUDIO diff --git a/subsys/bluetooth/audio/Kconfig.csis b/subsys/bluetooth/audio/Kconfig.csis new file mode 100644 index 00000000000..07ade0d8d9a --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.csis @@ -0,0 +1,68 @@ +# Bluetooth Audio - Broadcast Assistant configuration options +# +# Copyright (c) 2020 Bose Corporation +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +if BT_AUDIO + +#################### Coordinated Set Identification Service #################### + +config BT_CSIS + bool "Coordinated Set Identification Service Support [EXPERIMENTAL]" + imply BT_EXT_ADV if BT_PRIVACY + select EXPERIMENTAL + help + This option enables support for Coordinated Set Identification + Service. + +if BT_CSIS + +config BT_CSIS_TEST_SAMPLE_DATA + bool "Coordinated Set Identification Service Test Sample Data" + help + Enable the use of the sample data defined by the CSIS spec SIRK. + This will use the sample SIRK, prand and LTK. + + WARNING: This option enables anyone to track and decrypt the SIRK + (if encrypted) using public sample data. + Should not be used for production builds. + +config BT_CSIS_ENC_SIRK_SUPPORT + bool "Support for encrypted SIRK" + default y + select BT_SMP_SC_PAIR_ONLY + help + Enables support encrypting the SIRK. + +config BT_CSIS_MAX_INSTANCE_COUNT + int "Coordinated Set Identification Service max instance count" + default 1 + range 1 255 + help + This option sets the maximum number of instances of Coordinated Set + Identification Services. If the service is declared as primary service + then only a single instance is possible. + +############### DEBUG ############### + +config BT_DEBUG_CSIS + bool "Coordinated Set Identification Service debug" + depends on BT_AUDIO_DEBUG + help + Use this option to enable Coordinated Set Identification Service debug + logs for the Bluetooth Audio functionality. + +endif # BT_CSIS + +config BT_DEBUG_CSIS_CRYPTO + bool "Coordinated Set Identification Service crypto functions debug" + depends on BT_AUDIO_DEBUG + depends on BT_CSIS + help + Use this option to enable Coordinated Set Identification Service + crypto functions debug logs for the Bluetooth Audio functionality. + +endif # BT_AUDIO diff --git a/subsys/bluetooth/audio/csis.c b/subsys/bluetooth/audio/csis.c new file mode 100644 index 00000000000..97882633fc6 --- /dev/null +++ b/subsys/bluetooth/audio/csis.c @@ -0,0 +1,996 @@ +/* Bluetooth CSIS - Coordinated Set Identification Service */ + +/* + * Copyright (c) 2019 Bose Corporation + * Copyright (c) 2020-2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "csis_internal.h" +#include "csis_crypto.h" +#include "../host/conn_internal.h" +#include "../host/hci_core.h" +#include "../host/keys.h" + +#define BT_CSIS_SIH_PRAND_SIZE 3 +#define BT_CSIS_SIH_HASH_SIZE 3 +#define CSIS_SET_LOCK_TIMER_VALUE K_SECONDS(60) +#if defined(CONFIG_BT_PRIVACY) +/* The ADV time (in tens of milliseconds). Shall be less than the RPA. + * Make it relatively smaller (90%) to handle all ranges. Maximum value is + * 2^16 - 1 (UINT16_MAX). + */ +#define CSIS_ADV_TIME (MIN((CONFIG_BT_RPA_TIMEOUT * 100 * 0.9), UINT16_MAX)) +#else +/* Without privacy, connectable adv won't update the address when restarting, + * so we might as well continue advertising non-stop. + */ +#define CSIS_ADV_TIME 0 +#endif /* CONFIG_BT_PRIVACY */ + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CSIS) +#define LOG_MODULE_NAME bt_csis +#include "common/log.h" + +#if defined(CONFIG_BT_RPA) && !defined(CONFIG_BT_BONDABLE) +#define SIRK_READ_PERM (BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_READ_ENCRYPT) +#else +#define SIRK_READ_PERM (BT_GATT_PERM_READ_ENCRYPT) +#endif + +static struct bt_csis_cb *csis_cbs; + +static struct bt_csis csis_insts[CONFIG_BT_CSIS_MAX_INSTANCE_COUNT]; +static bt_addr_le_t server_dummy_addr; /* 0'ed address */ + +struct csis_notify_foreach { + struct bt_conn *excluded_client; + struct bt_csis *csis; +}; + +static bool is_last_client_to_write(const struct bt_csis *csis, + const struct bt_conn *conn) +{ + if (conn != NULL) { + return !bt_addr_le_cmp(bt_conn_get_dst(conn), + &csis->srv.lock_client_addr); + } else { + return !bt_addr_le_cmp(&server_dummy_addr, + &csis->srv.lock_client_addr); + } +} + +static void notify_lock_value(const struct bt_csis *csis, struct bt_conn *conn) +{ + bt_gatt_notify_uuid(conn, BT_UUID_CSIS_SET_LOCK, + csis->srv.service_p->attrs, + &csis->srv.set_lock, + sizeof(csis->srv.set_lock)); +} + +static void notify_client(struct bt_conn *conn, void *data) +{ + struct csis_notify_foreach *csis_data = (struct csis_notify_foreach *)data; + struct bt_csis *csis = csis_data->csis; + struct bt_conn *excluded_conn = csis_data->excluded_client; + + if (excluded_conn != NULL && conn == excluded_conn) { + return; + } + + notify_lock_value(csis, conn); + + for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[i]; + + if (pend_notify->pending && + bt_addr_le_cmp(bt_conn_get_dst(conn), + &pend_notify->addr) == 0) { + pend_notify->pending = false; + break; + } + } +} + +static void notify_clients(struct bt_csis *csis, + struct bt_conn *excluded_client) +{ + struct csis_notify_foreach data = { + .excluded_client = excluded_client, + .csis = csis, + }; + + /* Mark all bonded devices as pending notifications, and clear those + * that are notified in `notify_client` + */ + for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[i]; + + if (pend_notify->active) { + if (excluded_client != NULL && + bt_addr_le_cmp(bt_conn_get_dst(excluded_client), + &pend_notify->addr) == 0) { + continue; + } + + pend_notify->pending = true; + } + } + + bt_conn_foreach(BT_CONN_TYPE_ALL, notify_client, &data); +} + +static int sirk_encrypt(struct bt_conn *conn, + const struct bt_csis_set_sirk *sirk, + struct bt_csis_set_sirk *enc_sirk) +{ + int err; + uint8_t *k; + + if (IS_ENABLED(CONFIG_BT_CSIS_TEST_SAMPLE_DATA)) { + /* test_k is from the sample data from A.2 in the CSIS spec */ + static uint8_t test_k[] = {0x67, 0x6e, 0x1b, 0x9b, + 0xd4, 0x48, 0x69, 0x6f, + 0x06, 0x1e, 0xc6, 0x22, + 0x3c, 0xe5, 0xce, 0xd9}; + static bool swapped; + + if (!swapped && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) { + /* Swap test_k to little endian */ + sys_mem_swap(test_k, 16); + swapped = true; + } + BT_DBG("Encrypting test SIRK"); + k = test_k; + } else { + k = conn->le.keys->ltk.val; + } + + err = bt_csis_sef(k, sirk->value, enc_sirk->value); + + if (err != 0) { + return err; + } + + enc_sirk->type = BT_CSIS_SIRK_TYPE_ENCRYPTED; + + return 0; +} + +static int generate_prand(uint32_t *dest) +{ + bool valid = false; + + do { + int res; + + *dest = 0; + res = bt_rand(dest, BT_CSIS_SIH_PRAND_SIZE); + if (res != 0) { + return res; + } + + /* Validate Prand: Must contain both a 1 and a 0 */ + if (*dest != 0 && *dest != 0x3FFFFF) { + valid = true; + } + } while (!valid); + + *dest &= 0x3FFFFF; + *dest |= BIT(22); /* bit 23 shall be 0, and bit 22 shall be 1 */ + + return 0; +} + +static int csis_update_psri(struct bt_csis *csis) +{ + int res = 0; + uint32_t prand; + uint32_t hash; + + if (IS_ENABLED(CONFIG_BT_CSIS_TEST_SAMPLE_DATA)) { + /* prand is from the sample data from A.2 in the CSIS spec */ + prand = 0x69f563; + } else { + res = generate_prand(&prand); + + if (res != 0) { + BT_WARN("Could not generate new prand"); + return res; + } + } + + res = bt_csis_sih(csis->srv.set_sirk.value, prand, &hash); + if (res != 0) { + BT_WARN("Could not generate new PSRI"); + return res; + } + + (void)memcpy(csis->srv.psri, &hash, BT_CSIS_SIH_HASH_SIZE); + (void)memcpy(csis->srv.psri + BT_CSIS_SIH_HASH_SIZE, &prand, + BT_CSIS_SIH_PRAND_SIZE); + return res; +} + +int csis_adv_resume(struct bt_csis *csis) +{ + int err; + struct bt_data ad[2] = { + BT_DATA_BYTES(BT_DATA_FLAGS, + BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR) + }; + + BT_DBG("Restarting CSIS advertising"); + + if (csis_update_psri(csis) != 0) { + return -EAGAIN; + } + + ad[1].type = BT_DATA_CSIS_RSI; + ad[1].data_len = sizeof(csis->srv.psri); + ad[1].data = csis->srv.psri; + +#if defined(CONFIG_BT_EXT_ADV) + struct bt_le_ext_adv_start_param start_param; + + if (csis->srv.adv == NULL) { + struct bt_le_adv_param param; + + (void)memset(¶m, 0, sizeof(param)); + param.options |= BT_LE_ADV_OPT_CONNECTABLE; + param.options |= BT_LE_ADV_OPT_SCANNABLE; + param.options |= BT_LE_ADV_OPT_USE_NAME; + + param.id = BT_ID_DEFAULT; + param.sid = 0; + param.interval_min = BT_GAP_ADV_FAST_INT_MIN_2; + param.interval_max = BT_GAP_ADV_FAST_INT_MAX_2; + + err = bt_le_ext_adv_create(¶m, &csis->srv.adv_cb, + &csis->srv.adv); + if (err != 0) { + BT_DBG("Could not create adv set: %d", err); + return err; + } + } + + err = bt_le_ext_adv_set_data(csis->srv.adv, ad, ARRAY_SIZE(ad), NULL, + 0); + + if (err != 0) { + BT_DBG("Could not set adv data: %d", err); + return err; + } + + (void)memset(&start_param, 0, sizeof(start_param)); + start_param.timeout = CSIS_ADV_TIME; + err = bt_le_ext_adv_start(csis->srv.adv, &start_param); +#else + err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0); +#endif /* CONFIG_BT_EXT_ADV */ + + if (err != 0) { + BT_DBG("Could not start adv: %d", err); + return err; + } + + return err; +} + +static ssize_t read_set_sirk(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct bt_csis_set_sirk enc_sirk; + struct bt_csis_set_sirk *sirk; + struct bt_csis *csis = attr->user_data; + + if (csis_cbs != NULL && csis_cbs->sirk_read_req != NULL) { + uint8_t cb_rsp; + + /* Ask higher layer for what SIRK to return, if any */ + cb_rsp = csis_cbs->sirk_read_req(conn, &csis_insts[0]); + + if (cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT) { + sirk = &csis->srv.set_sirk; + } else if (IS_ENABLED(CONFIG_BT_CSIS_ENC_SIRK_SUPPORT) && + cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT_ENC) { + int err; + + err = sirk_encrypt(conn, &csis->srv.set_sirk, + &enc_sirk); + if (err != 0) { + BT_ERR("Could not encrypt SIRK: %d", + err); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + sirk = &enc_sirk; + BT_HEXDUMP_DBG(enc_sirk.value, sizeof(enc_sirk.value), + "Encrypted Set SIRK"); + } else if (cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_REJECT) { + return BT_GATT_ERR(BT_CSIS_ERROR_SIRK_ACCESS_REJECTED); + } else if (cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_OOB_ONLY) { + return BT_GATT_ERR(BT_CSIS_ERROR_SIRK_OOB_ONLY); + } + + BT_ERR("Invalid callback response: %u", cb_rsp); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + sirk = &csis->srv.set_sirk; + + BT_DBG("Set sirk %sencrypted", + sirk->type == BT_CSIS_SIRK_TYPE_PLAIN ? "not " : ""); + BT_HEXDUMP_DBG(csis->srv.set_sirk.value, + sizeof(csis->srv.set_sirk.value), "Set SIRK"); + return bt_gatt_attr_read(conn, attr, buf, len, offset, + sirk, sizeof(*sirk)); +} + +static void set_sirk_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + BT_DBG("value 0x%04x", value); +} + +static ssize_t read_set_size(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct bt_csis *csis = attr->user_data; + + BT_DBG("%u", csis->srv.set_size); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &csis->srv.set_size, + sizeof(csis->srv.set_size)); +} + +static void set_size_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + BT_DBG("value 0x%04x", value); +} + +static ssize_t read_set_lock(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct bt_csis *csis = attr->user_data; + + BT_DBG("%u", csis->srv.set_lock); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &csis->srv.set_lock, + sizeof(csis->srv.set_lock)); +} + +static ssize_t write_set_lock(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + uint8_t val; + bool notify; + struct bt_csis *csis = attr->user_data; + + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } else if (len != sizeof(val)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + (void)memcpy(&val, buf, len); + + if (val != BT_CSIS_RELEASE_VALUE && val != BT_CSIS_LOCK_VALUE) { + return BT_GATT_ERR(BT_CSIS_ERROR_LOCK_INVAL_VALUE); + } + + if (csis->srv.set_lock == BT_CSIS_LOCK_VALUE) { + if (val == BT_CSIS_LOCK_VALUE) { + if (is_last_client_to_write(csis, conn)) { + return BT_GATT_ERR( + BT_CSIS_ERROR_LOCK_ALREADY_GRANTED); + } else { + return BT_GATT_ERR(BT_CSIS_ERROR_LOCK_DENIED); + } + } else if (!is_last_client_to_write(csis, conn)) { + return BT_GATT_ERR(BT_CSIS_ERROR_LOCK_RELEASE_DENIED); + } + } + + notify = csis->srv.set_lock != val; + + csis->srv.set_lock = val; + if (csis->srv.set_lock == BT_CSIS_LOCK_VALUE) { + if (conn != NULL) { + bt_addr_le_copy(&csis->srv.lock_client_addr, + bt_conn_get_dst(conn)); + } + (void)k_work_reschedule(&csis->srv.set_lock_timer, + CSIS_SET_LOCK_TIMER_VALUE); + } else { + (void)memset(&csis->srv.lock_client_addr, 0, + sizeof(csis->srv.lock_client_addr)); + (void)k_work_cancel_delayable(&csis->srv.set_lock_timer); + } + + BT_DBG("%u", csis->srv.set_lock); + + if (notify) { + /* + * The Spec states that all clients, except for the + * client writing the value, shall be notified + * (if subscribed) + */ + notify_clients(csis, conn); + + if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) { + bool locked = csis->srv.set_lock == BT_CSIS_LOCK_VALUE; + + csis_cbs->lock_changed(conn, csis, locked); + } + } + return len; +} + +static void set_lock_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + BT_DBG("value 0x%04x", value); +} + +static ssize_t read_rank(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct bt_csis *csis = attr->user_data; + + BT_DBG("%u", csis->srv.rank); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &csis->srv.rank, + sizeof(csis->srv.rank)); + +} + +static void set_lock_timer_handler(struct k_work *work) +{ + struct k_work_delayable *delayable; + struct bt_csis_server *server; + struct bt_csis *csis; + + delayable = CONTAINER_OF(work, struct k_work_delayable, work); + server = CONTAINER_OF(delayable, struct bt_csis_server, set_lock_timer); + csis = CONTAINER_OF(server, struct bt_csis, srv); + + BT_DBG("Lock timeout, releasing"); + csis->srv.set_lock = BT_CSIS_RELEASE_VALUE; + notify_clients(csis, NULL); + + if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) { + bool locked = csis->srv.set_lock == BT_CSIS_LOCK_VALUE; + + csis_cbs->lock_changed(NULL, csis, locked); + } +} + +static void csis_security_changed(struct bt_conn *conn, bt_security_t level, + enum bt_security_err err) +{ + if (err != 0 || conn->encrypt == 0) { + return; + } + + if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) { + return; + } + + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + struct bt_csis *csis = &csis_insts[i]; + + for (int j = 0; j < ARRAY_SIZE(csis->srv.pend_notify); j++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[j]; + + if (pend_notify->pending && + bt_addr_le_cmp(bt_conn_get_dst(conn), + &pend_notify->addr) == 0) { + notify_lock_value(csis, conn); + pend_notify->pending = false; + break; + } + } + } +} + +#if defined(CONFIG_BT_EXT_ADV) +static void csis_connected(struct bt_conn *conn, uint8_t err) +{ + if (err == BT_HCI_ERR_SUCCESS) { + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + struct bt_csis *csis = &csis_insts[i]; + + csis->srv.conn_cnt++; + + __ASSERT(csis->srv.conn_cnt <= CONFIG_BT_MAX_CONN, + "Invalid csis->srv.conn_cnt value"); + } + } +} + +static void disconnect_adv(struct k_work *work) +{ + int err; + struct bt_csis_server *server = CONTAINER_OF(work, struct bt_csis_server, work); + struct bt_csis *csis = CONTAINER_OF(server, struct bt_csis, srv); + + err = csis_adv_resume(csis); + + if (err != 0) { + BT_ERR("Disconnect: Could not restart advertising: %d", + err); + csis->srv.adv_enabled = false; + } +} +#endif /* CONFIG_BT_EXT_ADV */ + +static void handle_csis_disconnect(struct bt_csis *csis, struct bt_conn *conn) +{ +#if defined(CONFIG_BT_EXT_ADV) + __ASSERT(csis->srv.conn_cnt != 0, "Invalid csis->srv.conn_cnt value"); + + if (csis->srv.conn_cnt == CONFIG_BT_MAX_CONN && + csis->srv.adv_enabled) { + /* A connection spot opened up */ + k_work_submit(&csis->srv.work); + } + csis->srv.conn_cnt--; +#endif /* CONFIG_BT_EXT_ADV */ + + BT_DBG("Non-bonded device"); + if (is_last_client_to_write(csis, conn)) { + (void)memset(&csis->srv.lock_client_addr, 0, + sizeof(csis->srv.lock_client_addr)); + csis->srv.set_lock = BT_CSIS_RELEASE_VALUE; + notify_clients(csis, NULL); + + if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) { + bool locked = csis->srv.set_lock == BT_CSIS_LOCK_VALUE; + + csis_cbs->lock_changed(conn, csis, locked); + } + } + + /* Check if the disconnected device once was bonded and stored + * here as a bonded device + */ + for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[i]; + + if (bt_addr_le_cmp(bt_conn_get_dst(conn), + &pend_notify->addr) == 0) { + (void)memset(pend_notify, 0, sizeof(*pend_notify)); + break; + } + } +} + +static void csis_disconnected(struct bt_conn *conn, uint8_t reason) +{ + BT_DBG("Disconnected: %s (reason %u)", + bt_addr_le_str(bt_conn_get_dst(conn)), reason); + + /* + * If lock was taken by non-bonded device, set lock to released value, + * and notify other connections. + */ + if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) { + return; + } + + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + handle_csis_disconnect(&csis_insts[i], conn); + } +} + +static void handle_csis_auth_complete(struct bt_csis *csis, + struct bt_conn *conn) +{ + /* Check if already in list, and do nothing if it is */ + for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[i]; + + if (pend_notify->active && + bt_addr_le_cmp(bt_conn_get_dst(conn), + &pend_notify->addr) == 0) { +#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + pend_notify->age = csis->srv.age_counter++; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + return; + } + } + + /* Copy addr to list over devices to save notifications for */ + for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[i]; + + if (!pend_notify->active) { + bt_addr_le_copy(&pend_notify->addr, + bt_conn_get_dst(conn)); + pend_notify->active = true; +#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + pend_notify->age = csis->srv.age_counter++; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + return; + } + } + +#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + struct csis_pending_notifications *oldest; + + oldest = &csis->srv.pend_notify[0]; + + for (int i = 1; i < ARRAY_SIZE(csis->srv.pend_notify); i++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[i]; + + if (pend_notify->age < oldest->age) { + oldest = pend_notify; + } + } + (void)memset(oldest, 0, sizeof(*oldest)); + bt_addr_le_copy(&oldest->addr, &conn->le.dst); + oldest->active = true; + oldest->age = csis->srv.age_counter++; +#else + BT_WARN("Could not add device to pending notification list"); +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + +} + +static void auth_pairing_complete(struct bt_conn *conn, bool bonded) +{ + /** + * If a pairing is complete for a bonded device, then we + * 1) Store the connection pointer to later validate SIRK encryption + * 2) Check if the device is already in the `pend_notify`, and if it is + * not, then we + * 3) Check if there's room for another device in the `pend_notify` + * array. If there are no more room for a new device, then + * 4) Either we ignore this new device (bad luck), or we overwrite + * the oldest entry, following the behavior of the key storage. + */ + + BT_DBG("%s paired (%sbonded)", + bt_addr_le_str(bt_conn_get_dst(conn)), bonded ? "" : "not "); + + if (!bonded) { + return; + } + + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + handle_csis_auth_complete(&csis_insts[i], conn); + } +} + +static void csis_bond_deleted(uint8_t id, const bt_addr_le_t *peer) +{ + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + struct bt_csis *csis = &csis_insts[i]; + + for (int j = 0; j < ARRAY_SIZE(csis->srv.pend_notify); j++) { + struct csis_pending_notifications *pend_notify; + + pend_notify = &csis->srv.pend_notify[j]; + + if (pend_notify->active && + bt_addr_le_cmp(peer, &pend_notify->addr) == 0) { + (void)memset(pend_notify, 0, + sizeof(*pend_notify)); + break; + } + } + } +} + +static struct bt_conn_cb conn_callbacks = { + +#if defined(CONFIG_BT_EXT_ADV) + .connected = csis_connected, +#endif /* CONFIG_BT_EXT_ADV */ + .disconnected = csis_disconnected, + .security_changed = csis_security_changed, +}; + +static const struct bt_conn_auth_cb auth_callbacks = { + .pairing_complete = auth_pairing_complete, + .bond_deleted = csis_bond_deleted +}; + +#if defined(CONFIG_BT_EXT_ADV) +/* TODO: Temp fix due to bug in adv callbacks: + * https://github.com/zephyrproject-rtos/zephyr/issues/30699 + */ +static bool conn_based_timeout; +static void adv_timeout(struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_sent_info *info) +{ + struct bt_csis *csis = NULL; + + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + if (adv == csis_insts[i].srv.adv) { + csis = &csis_insts[i]; + break; + } + } + __ASSERT(csis != NULL, "Could not find CSIS instance by ADV set %p", + adv); + + if (conn_based_timeout) { + return; + } + conn_based_timeout = false; + + /* Restart to update RSI value with new private address */ + if (csis->srv.adv_enabled) { + int err = csis_adv_resume(csis); + + if (err != 0) { + BT_ERR("Timeout: Could not restart advertising: %d", + err); + csis->srv.adv_enabled = false; + } + } +} + +static void adv_connected(struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_connected_info *info) +{ + struct bt_csis *csis = NULL; + + for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) { + if (adv == csis_insts[i].srv.adv) { + csis = &csis_insts[i]; + break; + } + } + __ASSERT(csis != NULL, "Could not find CSIS instance by ADV set %p", + adv); + + if (csis->srv.conn_cnt < CONFIG_BT_MAX_CONN && + csis->srv.adv_enabled) { + int err = csis_adv_resume(csis); + + if (err != 0) { + BT_ERR("Connected: Could not restart advertising: %d", + err); + csis->srv.adv_enabled = false; + } + } + + conn_based_timeout = true; +} +#endif /* CONFIG_BT_EXT_ADV */ + +#define BT_CSIS_SERVICE_DEFINITION(_csis) {\ + BT_GATT_PRIMARY_SERVICE(BT_UUID_CSIS), \ + BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_SET_SIRK, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + SIRK_READ_PERM, \ + read_set_sirk, NULL, &_csis), \ + BT_GATT_CCC(set_sirk_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \ + BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_SET_SIZE, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_set_size, NULL, &_csis), \ + BT_GATT_CCC(set_size_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \ + BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_SET_LOCK, \ + BT_GATT_CHRC_READ | \ + BT_GATT_CHRC_NOTIFY | \ + BT_GATT_CHRC_WRITE, \ + BT_GATT_PERM_READ_ENCRYPT | \ + BT_GATT_PERM_WRITE_ENCRYPT, \ + read_set_lock, write_set_lock, &_csis), \ + BT_GATT_CCC(set_lock_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \ + BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_RANK, \ + BT_GATT_CHRC_READ, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_rank, NULL, &_csis) \ + } + +BT_GATT_SERVICE_INSTANCE_DEFINE(csis_service_list, csis_insts, + CONFIG_BT_CSIS_MAX_INSTANCE_COUNT, + BT_CSIS_SERVICE_DEFINITION); + +/****************************** Public API ******************************/ +void *bt_csis_svc_decl_get(const struct bt_csis *csis) +{ + return csis->srv.service_p->attrs; +} + +static bool valid_register_param(const struct bt_csis_register_param *param) +{ + if (param->lockable && param->rank == 0) { + BT_DBG("Rank cannot be 0 if service is lockable"); + return false; + } + + if (param->rank > 0 && param->rank > param->set_size) { + BT_DBG("Invalid rank: %u (shall be less than set_size: %u)", + param->set_size, param->set_size); + return false; + } + + if (param->set_size > 0 && param->set_size < BT_CSIS_MINIMUM_SET_SIZE) { + BT_DBG("Invalid set size: %u", param->set_size); + return false; + } + + return true; +} + +int bt_csis_register(const struct bt_csis_register_param *param, + struct bt_csis **csis) +{ + static uint8_t instance_cnt; + struct bt_csis *inst; + int err; + + if (instance_cnt == ARRAY_SIZE(csis_insts)) { + return -ENOMEM; + } + + CHECKIF(param == NULL) { + BT_DBG("NULL param"); + return -EINVAL; + } + + CHECKIF(!valid_register_param(param)) { + BT_DBG("Invalid parameters"); + return -EINVAL; + } + + inst = &csis_insts[instance_cnt]; + inst->srv.service_p = &csis_service_list[instance_cnt]; + instance_cnt++; + + bt_conn_cb_register(&conn_callbacks); + bt_conn_auth_cb_register(&auth_callbacks); + + err = bt_gatt_service_register(inst->srv.service_p); + if (err != 0) { + BT_DBG("CSIS service register failed: %d", err); + return err; + } + + k_work_init_delayable(&inst->srv.set_lock_timer, + set_lock_timer_handler); + inst->srv.rank = param->rank; + inst->srv.set_size = param->set_size; + inst->srv.set_lock = BT_CSIS_RELEASE_VALUE; + inst->srv.set_sirk.type = BT_CSIS_SIRK_TYPE_PLAIN; + + if (IS_ENABLED(CONFIG_BT_CSIS_TEST_SAMPLE_DATA)) { + uint8_t test_sirk[] = { + 0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, + 0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45, + }; + + (void)memcpy(inst->srv.set_sirk.value, test_sirk, + sizeof(test_sirk)); + BT_DBG("CSIS SIRK was overwritten by sample data SIRK"); + } else { + (void)memcpy(inst->srv.set_sirk.value, param->set_sirk, + sizeof(inst->srv.set_sirk.value)); + } + +#if defined(CONFIG_BT_EXT_ADV) + inst->srv.adv_cb.sent = adv_timeout; + inst->srv.adv_cb.connected = adv_connected; + k_work_init(&inst->srv.work, disconnect_adv); +#endif /* CONFIG_BT_EXT_ADV */ + + *csis = inst; + return 0; +} + +int bt_csis_advertise(struct bt_csis *csis, bool enable) +{ + int err; + + if (enable) { + if (csis->srv.adv_enabled) { + return -EALREADY; + } + + err = csis_adv_resume(csis); + + if (err != 0) { + BT_DBG("Could not start adv: %d", err); + return err; + } + csis->srv.adv_enabled = true; + } else { + if (!csis->srv.adv_enabled) { + return -EALREADY; + } +#if defined(CONFIG_BT_EXT_ADV) + err = bt_le_ext_adv_stop(csis->srv.adv); +#else + err = bt_le_adv_stop(); +#endif /* CONFIG_BT_EXT_ADV */ + if (err != 0) { + BT_DBG("Could not stop start adv: %d", err); + return err; + } + csis->srv.adv_enabled = false; + } + + return err; +} + +int bt_csis_lock(struct bt_csis *csis, bool lock, bool force) +{ + uint8_t lock_val; + int err = 0; + + if (lock) { + lock_val = BT_CSIS_LOCK_VALUE; + } else { + lock_val = BT_CSIS_RELEASE_VALUE; + } + + if (!lock && force) { + csis->srv.set_lock = BT_CSIS_RELEASE_VALUE; + notify_clients(csis, NULL); + + if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) { + csis_cbs->lock_changed(NULL, &csis_insts[0], false); + } + } else { + err = write_set_lock(NULL, NULL, &lock_val, sizeof(lock_val), 0, + 0); + } + + if (err < 0) { + return err; + } else { + return 0; + } +} + +void bt_csis_print_sirk(const struct bt_csis *csis) +{ + BT_HEXDUMP_DBG(&csis->srv.set_sirk, sizeof(csis->srv.set_sirk), + "Set SIRK"); +} diff --git a/subsys/bluetooth/audio/csis.h b/subsys/bluetooth/audio/csis.h new file mode 100644 index 00000000000..44f22c8e1ba --- /dev/null +++ b/subsys/bluetooth/audio/csis.h @@ -0,0 +1,183 @@ +/** + * @file + * @brief APIs for Bluetooth CSIS + * + * Copyright (c) 2019 Bose Corporation + * Copyright (c) 2020-2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_CSIS_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_CSIS_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BT_CSIS_MINIMUM_SET_SIZE 2 +#define BT_CSIS_PSRI_SIZE 6 + +/** Accept the request to read the SIRK as plaintext */ +#define BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT 0x00 +/** Accept the request to read the SIRK, but return encrypted SIRK */ +#define BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT_ENC 0x01 +/** Reject the request to read the SIRK */ +#define BT_CSIS_READ_SIRK_REQ_RSP_REJECT 0x02 +/** SIRK is available only via an OOB procedure */ +#define BT_CSIS_READ_SIRK_REQ_RSP_OOB_ONLY 0x03 + +#define BT_CSIS_SET_SIRK_SIZE 16 + +#define BT_CSIS_ERROR_LOCK_DENIED 0x80 +#define BT_CSIS_ERROR_LOCK_RELEASE_DENIED 0x81 +#define BT_CSIS_ERROR_LOCK_INVAL_VALUE 0x82 +#define BT_CSIS_ERROR_SIRK_ACCESS_REJECTED 0x83 +#define BT_CSIS_ERROR_SIRK_OOB_ONLY 0x84 +#define BT_CSIS_ERROR_LOCK_ALREADY_GRANTED 0x85 + +#define BT_CSIS_RELEASE_VALUE 0x01 +#define BT_CSIS_LOCK_VALUE 0x02 + +#define BT_CSIS_SIRK_TYPE_ENCRYPTED 0x00 +#define BT_CSIS_SIRK_TYPE_PLAIN 0x01 + +/** @brief Opaque Coordinated Set Identification Service instance. */ +struct bt_csis; + +struct bt_csis_cb { + /** + * @brief Callback whenever the lock changes on the server. + * + * @param conn The connection to the client that changed the lock. + * NULL if server changed it, either by calling + * @ref csis_lock or by timeout. + * @param csis Pointer to the Coordinated Set Identification Service. + * @param locked Whether the lock was locked or released. + * + */ + void (*lock_changed)(struct bt_conn *conn, struct bt_csis *csis, + bool locked); + + /** + * @brief Request from a peer device to read the sirk. + * + * If this callback is not set, all clients will be allowed to read + * the SIRK unencrypted. + * + * @param conn The connection to the client that requested to read the + * SIRK. + * @param csis Pointer to the Coordinated Set Identification Service. + * + * @return A BT_CSIS_READ_SIRK_REQ_RSP_* response code. + */ + uint8_t (*sirk_read_req)(struct bt_conn *conn, struct bt_csis *csis); +}; + +/** Register structure for Coordinated Set Identification Service */ +struct bt_csis_register_param { + /** + * @brief Size of the set. + * + * If set to 0, the set size characteric won't be initialized. + * Otherwise shall be set to minimum 2. + */ + uint8_t set_size; + + /** + * @brief The unique Set Identity Resolving Key (SIRK) + * + * This shall be unique between different sets, and shall be the same + * for each set member for each set. + */ + uint8_t set_sirk[BT_CSIS_SET_SIRK_SIZE]; + + /** + * @brief Boolean to set whether the set is lockable by clients + * + * Setting this to false will disable the lock characteristic. + */ + bool lockable; + + /** + * @brief Rank of this device in this set. + * + * If the lockable parameter is set to true, this shall be > 0 and + * <= to the set_size. If the lockable parameter is set to false, this + * may be set to 0 to disable the rank characteristic. + */ + uint8_t rank; + + /** Pointer to the callback structure. */ + struct bt_csis_cb *cb; +}; + +/** + * @brief Get the service declaration attribute. + * + * The first service attribute can be included in any other GATT service. + * + * @param csis Pointer to the Coordinated Set Identification Service. + * + * @return The first CSIS attribute instance. + */ +void *bt_csis_svc_decl_get(const struct bt_csis *csis); + +/** + * @brief Register the Coordinated Set Identification Service. + * + * This will register and enable the service and make it discoverable by + * clients. + * + * This shall only be done as a server. + * + * @param param Coordinated Set Identification Service register parameters. + * @param[out] csis Pointer to the registered Coordinated Set Identification + * Service. + * + * @return 0 if success, errno on failure. + */ +int bt_csis_register(const struct bt_csis_register_param *param, + struct bt_csis **csis); + +/** + * @brief Print the sirk to the debug output + * + * @param csis Pointer to the Coordinated Set Identification Service. + */ +void bt_csis_print_sirk(const struct bt_csis *csis); + +/** + * @brief Starts advertising the PRSI value. + * + * This cannot be used with other connectable advertising sets. + * + * @param csis Pointer to the Coordinated Set Identification Service. + * @param enable If true start advertising, if false stop advertising + * + * @return int 0 if on success, ERRNO on error. + */ +int bt_csis_advertise(struct bt_csis *csis, bool enable); + +/** + * @brief Locks the sets on the server. + * + * @param csis Pointer to the Coordinated Set Identification Service. + * @param lock If true lock the set, if false release the set. + * @param force This argument only have meaning when @p lock is false + * (release) and will force release the lock, regardless of who + * took the lock. + * + * @return 0 on success, GATT error on error. + */ +int bt_csis_lock(struct bt_csis *csis, bool lock, bool force); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_CSIS_H_ */ diff --git a/subsys/bluetooth/audio/csis_crypto.c b/subsys/bluetooth/audio/csis_crypto.c new file mode 100644 index 00000000000..5686a3de714 --- /dev/null +++ b/subsys/bluetooth/audio/csis_crypto.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2020 Bose Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + * + * The static functions in this file operate on Big Endian (BE) as the + * underlying encryption library is BE as well. Furthermore, the sample data + * in the CSIS spec is also provided as BE, and logging values as BE will make + * it easier to compare. + */ +#include "csis_crypto.h" +#include +#include +#include +#include +#include +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CSIS_CRYPTO) +#define LOG_MODULE_NAME bt_csis_crypto +#include "common/log.h" + +#define BT_CSIS_CRYPTO_PADDING_SIZE 13 +#define BT_CSIS_R_SIZE 3 /* r is 24 bit / 3 octet */ +#define BT_CSIS_R_MASK BIT_MASK(24) /* r is 24 bit / 3 octet */ + +static int aes_cmac(const uint8_t key[BT_CSIS_CRYPTO_KEY_SIZE], + const uint8_t *in, size_t in_len, uint8_t *out) +{ + struct tc_aes_key_sched_struct sched; + struct tc_cmac_struct state; + + /* TODO: Copy of the aes_cmac from smp.c: Can we merge them? */ + + if (tc_cmac_setup(&state, key, &sched) == TC_CRYPTO_FAIL) { + return -EIO; + } + + if (tc_cmac_update(&state, in, in_len) == TC_CRYPTO_FAIL) { + return -EIO; + } + + if (tc_cmac_final(out, &state) == TC_CRYPTO_FAIL) { + return -EIO; + } + + return 0; +} + +static void xor_128(const uint8_t a[16], const uint8_t b[16], uint8_t out[16]) +{ + size_t len = 16; + /* TODO: Identical to the xor_128 from smp.c: Move to util */ + + while (len--) { + *out++ = *a++ ^ *b++; + } +} + +int bt_csis_sih(const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE], uint32_t r, + uint32_t *out) +{ + uint8_t res[16]; /* need to store 128 bit */ + int err; + uint8_t sirk_tmp[BT_CSIS_SET_SIRK_SIZE]; + + if ((r & BIT(23)) || ((r & BIT(22)) == 0)) { + BT_DBG("Invalid r %0x06x", (uint32_t)(r & BT_CSIS_R_MASK)); + } + + BT_DBG("SIRK %s", bt_hex(sirk, BT_CSIS_SET_SIRK_SIZE)); + BT_DBG("r 0x%06x", r); + + /* r' = padding || r */ + (void)memset(res, 0, BT_CSIS_CRYPTO_PADDING_SIZE); + sys_put_be24(r, res + BT_CSIS_CRYPTO_PADDING_SIZE); + + BT_DBG("BE: r' %s", bt_hex(res, sizeof(res))); + + if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) { + /* Swap to Big Endian (BE) */ + sys_memcpy_swap(sirk_tmp, sirk, BT_CSIS_SET_SIRK_SIZE); + } else { + (void)memcpy(sirk_tmp, sirk, BT_CSIS_SET_SIRK_SIZE); + } + + err = bt_encrypt_be(sirk_tmp, res, res); + + if (err != 0) { + return err; + } + + /* The output of the function sih is: + * sih(k, r) = e(k, r') mod 2^24 + * The output of the security function e is then truncated to 24 bits + * by taking the least significant 24 bits of the output of e as the + * result of sih. + */ + + BT_DBG("BE: res %s", bt_hex(res, sizeof(res))); + + /* Result is the lowest 3 bytes */ + *out = sys_get_be24(res + 13); + + BT_DBG("sih 0x%06x", *out); + + return 0; +} + +/** + * @brief k1 derivation function + * + * The key derivation function k1 is used to derive a key. The derived key is + * used to encrypt and decrypt the value of the Set Identity Resolving Key + * characteristic. + * + * @param n n is 0 or more bytes. + * @param n_size Number of bytes in @p n. + * @param salt A 16-byte salt. + * @param p p is 0 or more bytes. + * @param p_size Number of bytes in @p p. + * @param out A 16-byte output buffer. + * @return int 0 on success, any other value indicates a failure. + */ +static int k1(const uint8_t *n, size_t n_size, + const uint8_t salt[BT_CSIS_CRYPTO_SALT_SIZE], + const uint8_t *p, size_t p_size, uint8_t out[16]) +{ + /* TODO: This is basically a duplicate of bt_mesh_k1 - Perhaps they can + * be merged + */ + uint8_t t[16]; + int err; + + /* + * T = AES_CMAC_SALT(N) + * + * k1(N, SALT, P) = AES-CMAC_T(P) + */ + + BT_DBG("BE: n %s", bt_hex(n, n_size)); + BT_DBG("BE: salt %s", bt_hex(salt, BT_CSIS_CRYPTO_SALT_SIZE)); + BT_DBG("BE: p %s", bt_hex(p, p_size)); + + err = aes_cmac(salt, n, n_size, t); + + BT_DBG("BE: t %s", bt_hex(t, sizeof(t))); + + if (err) { + return err; + } + + err = aes_cmac(t, p, p_size, out); + + BT_DBG("BE: out %s", bt_hex(out, 16)); + + return err; +} + +/** + * @brief s1 SALT generation function + * + * @param m A non-zero length octet array or ASCII encoded string + * @param m_size Size of @p m. + * @param out 16-byte output buffer. + * @return int 0 on success, any other value indicates a failure. + */ +static int s1(const uint8_t *m, size_t m_size, + uint8_t out[BT_CSIS_CRYPTO_SALT_SIZE]) +{ + uint8_t zero[16]; + int err; + + /* + * s1(M) = AES-CMAC_zero(M) + */ + + BT_DBG("BE: m %s", bt_hex(m, m_size)); + + memset(zero, 0, sizeof(zero)); + + err = aes_cmac(zero, m, m_size, out); + + BT_DBG("BE: out %s", bt_hex(out, 16)); + + return err; +} + +int bt_csis_sef(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE], + const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE], + uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE]) +{ + const uint8_t m[] = {'S', 'I', 'R', 'K', 'e', 'n', 'c'}; + const uint8_t p[] = {'c', 's', 'i', 's'}; + uint8_t s1_out[BT_CSIS_CRYPTO_SALT_SIZE]; + uint8_t k1_out[BT_CSIS_CRYPTO_KEY_SIZE]; + uint8_t k1_tmp[BT_CSIS_CRYPTO_KEY_SIZE]; + int err; + + /* + * sef(K, SIRK) = k1(K, s1("SIRKenc"), "csis") ^ SIRK + */ + + BT_DBG("SIRK %s", bt_hex(sirk, BT_CSIS_SET_SIRK_SIZE)); + + if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) { + /* Swap because aes_cmac is big endian + * and we are little endian + */ + sys_memcpy_swap(k1_tmp, k, sizeof(k1_tmp)); + } else { + (void)memcpy(k1_tmp, k, sizeof(k1_tmp)); + } + BT_DBG("BE: k %s", bt_hex(k1_tmp, sizeof(k1_tmp))); + + err = s1(m, sizeof(m), s1_out); + if (err) { + return err; + } + + BT_DBG("BE: s1 result %s", bt_hex(s1_out, sizeof(s1_out))); + + err = k1(k1_tmp, sizeof(k1_tmp), s1_out, p, sizeof(p), k1_out); + if (err) { + return err; + } + + BT_DBG("BE: k1 result %s", bt_hex(k1_out, sizeof(k1_out))); + + if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) { + /* Swap result back to little endian */ + sys_mem_swap(k1_out, sizeof(k1_out)); + } + + xor_128(k1_out, sirk, out_sirk); + BT_DBG("out %s", bt_hex(out_sirk, BT_CSIS_SET_SIRK_SIZE)); + + return 0; +} + +int bt_csis_sdf(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE], + const uint8_t enc_sirk[BT_CSIS_SET_SIRK_SIZE], + uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE]) +{ + /* SIRK encryption is currently symmetric, which means that we can + * simply apply the sef function to decrypt it. + */ + + /* + * sdf(K, EncSIRK) = k1(K, s1("SIRKenc"), "csis") ^ EncSIRK + */ + + BT_DBG("Running SDF as SEF"); + return bt_csis_sef(k, enc_sirk, out_sirk); +} diff --git a/subsys/bluetooth/audio/csis_crypto.h b/subsys/bluetooth/audio/csis_crypto.h new file mode 100644 index 00000000000..2c5d52ec583 --- /dev/null +++ b/subsys/bluetooth/audio/csis_crypto.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Bose Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "csis.h" + +#define BT_CSIS_CRYPTO_KEY_SIZE 16 +#define BT_CSIS_CRYPTO_SALT_SIZE 16 + +/** + * @brief Private Set Unique identifier hash function sih. + * + * The PSRI hash function sih is used to generate a hash value that is + * used in PSRIs - Used by the Coordinated Set Identification service and + * profile. + * + * @param sirk The 16-byte SIRK + * @param r 3 byte random value + * @param out The 3 byte output buffer + * @return int 0 on success, any other value indicates a failure. + */ +int bt_csis_sih(const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE], uint32_t r, + uint32_t *out); + +/** + * @brief SIRK encryption function sef + * + * The SIRK encryption function sef is used by the server to encrypt the SIRK + * with a key K. The value of K depends on the transport on which the pairing + * between the client and the server was performed. + * + * If the pairing was performed on BR/EDR, K is equal to the Link Key shared by + * the server and the client. + * K = Link Key. + * + * If the pairing was performed on LE, the 64 LSBs of K correspond to the 64 + * LSBs of the IRK that the server sent to the client during the Phase 3 + * (Transport Specific Key Distribution) of the pairing procedure (see Volume 3, + * Part H, Section 2.1 in [2]), and the 64 MSBs of K correspond to the 64 MSBs + * of the LTK shared by the server and client. That is, + * K = LTK_64-127 || IRK_0-63 + * + * @param k 16-byte key. + * @param sirk The unencrypted SIRK. + * @param out_sirk The encrypted SIRK. + * @return int 0 on success, any other value indicates a failure. + */ +int bt_csis_sef(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE], + const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE], + uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE]); + +/** + * @brief SIRK decryption function sdf + * + * The SIRK decryption function sdf is used by the client to decrypt the SIRK + * with a key K. The value of K depends on the transport on which the pairing + * between the client and the server was performed. + * + * If the pairing was performed on BR/EDR, K is equal to the Link Key shared by + * the server and the client. + * K = Link Key. + * + * If the pairing was performed on LE, the 64 LSBs of K correspond to the 64 + * LSBs of the IRK that the server sent to the client during the Phase 3 + * (Transport Specific Key Distribution) of the pairing procedure (see Volume 3, + * Part H, Section 2.1 in [2]), and the 64 MSBs of K correspond to the 64 MSBs + * of the LTK shared by the server and client. That is, + * K = LTK_64-127 || IRK_0-63 + * + * @param k 16-byte key. + * @param sirk The encrypted SIRK. + * @param out_sirk The decrypted SIRK. + * @return int 0 on success, any other value indicates a failure. + */ +int bt_csis_sdf(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE], + const uint8_t enc_sirk[BT_CSIS_SET_SIRK_SIZE], + uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE]); diff --git a/subsys/bluetooth/audio/csis_internal.h b/subsys/bluetooth/audio/csis_internal.h new file mode 100644 index 00000000000..8cea9d63f4c --- /dev/null +++ b/subsys/bluetooth/audio/csis_internal.h @@ -0,0 +1,58 @@ +/** + * @file + * @brief Internal APIs for Bluetooth CSIS + * + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "csis.h" + +struct bt_csis_set_sirk { + uint8_t type; + uint8_t value[BT_CSIS_SET_SIRK_SIZE]; +} __packed; + +struct csis_pending_notifications { + bt_addr_le_t addr; + bool pending; + bool active; + +/* Since there's a 1-to-1 connection between bonded devices, and devices in + * the array containing this struct, if the security manager overwrites + * the oldest keys, we also overwrite the oldest entry + */ +#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + uint32_t age; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ +}; + +struct bt_csis_server { + struct bt_csis_set_sirk set_sirk; + uint8_t psri[BT_CSIS_PSRI_SIZE]; + uint8_t set_size; + uint8_t set_lock; + uint8_t rank; + bool adv_enabled; + struct k_work_delayable set_lock_timer; + bt_addr_le_t lock_client_addr; + struct bt_gatt_service *service_p; + struct csis_pending_notifications pend_notify[CONFIG_BT_MAX_PAIRED]; +#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + uint32_t age_counter; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ +#if defined(CONFIG_BT_EXT_ADV) + uint8_t conn_cnt; + struct bt_le_ext_adv *adv; + struct bt_le_ext_adv_cb adv_cb; + struct k_work work; +#endif /* CONFIG_BT_EXT_ADV */ +}; + +struct bt_csis { + union { + struct bt_csis_server srv; + /* TODO: Add cli */ + }; +}; diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index 17b03986476..0011834f4a6 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -54,3 +54,6 @@ CONFIG_BT_MICS=y CONFIG_BT_MICS_AICS_INSTANCE_COUNT=1 CONFIG_BT_MICS_CLIENT=y CONFIG_BT_MICS_CLIENT_MAX_AICS_INST=1 + +# Coordinated Set Identification +CONFIG_BT_CSIS=y