Bluetooth: Audio: Add coordinated sets identification service server

Adds the coordinated set identification service (CSIS) server.

This is still a work in progress and thus there are no public
API for it yet, and some code changes will still be needed.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2021-10-21 12:04:16 +02:00 committed by Carles Cufí
commit d55aeb4a1e
11 changed files with 1698 additions and 0 deletions

View file

@ -56,6 +56,7 @@ extern "C" {
#define BT_DATA_MESH_BEACON 0x2b /* Mesh Beacon */ #define BT_DATA_MESH_BEACON 0x2b /* Mesh Beacon */
#define BT_DATA_BIG_INFO 0x2c /* BIGInfo */ #define BT_DATA_BIG_INFO 0x2c /* BIGInfo */
#define BT_DATA_BROADCAST_CODE 0x2d /* Broadcast Code */ #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 */ #define BT_DATA_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */

View file

@ -446,6 +446,15 @@ struct bt_uuid_128 {
*/ */
#define BT_UUID_VOCS \ #define BT_UUID_VOCS \
BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL) 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 /** @def BT_UUID_MICS_VAL
* @brief Microphone Input Control Service value * @brief Microphone Input Control Service value
*/ */
@ -1484,6 +1493,42 @@ struct bt_uuid_128 {
*/ */
#define BT_UUID_VOCS_DESCRIPTION \ #define BT_UUID_VOCS_DESCRIPTION \
BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL) 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 /** @def BT_UUID_CCID_VAL
* @brief Content Control ID value * @brief Content Control ID value
*/ */

View file

@ -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_sources_ifdef(CONFIG_BT_CCID ccid.c)
zephyr_library_link_libraries(subsys__bluetooth) 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)

View file

@ -61,5 +61,6 @@ rsource "Kconfig.vocs"
rsource "Kconfig.aics" rsource "Kconfig.aics"
rsource "Kconfig.vcs" rsource "Kconfig.vcs"
rsource "Kconfig.mics" rsource "Kconfig.mics"
rsource "Kconfig.csis"
endif # BT_AUDIO endif # BT_AUDIO

View file

@ -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

View file

@ -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 <zephyr.h>
#include <zephyr/types.h>
#include <device.h>
#include <init.h>
#include <stdlib.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/gatt.h>
#include <bluetooth/buf.h>
#include <sys/byteorder.h>
#include <sys/check.h>
#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(&param, 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(&param, &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");
}

View file

@ -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 <zephyr/types.h>
#include <stdbool.h>
#include <bluetooth/conn.h>
#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_ */

View file

@ -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 <bluetooth/crypto.h>
#include <tinycrypt/constants.h>
#include <tinycrypt/utils.h>
#include <tinycrypt/aes.h>
#include <tinycrypt/cmac_mode.h>
#include <tinycrypt/ccm_mode.h>
#include <sys/byteorder.h>
#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);
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2019 Bose Corporation
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <zephyr/types.h>
#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]);

View file

@ -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 */
};
};

View file

@ -54,3 +54,6 @@ CONFIG_BT_MICS=y
CONFIG_BT_MICS_AICS_INSTANCE_COUNT=1 CONFIG_BT_MICS_AICS_INSTANCE_COUNT=1
CONFIG_BT_MICS_CLIENT=y CONFIG_BT_MICS_CLIENT=y
CONFIG_BT_MICS_CLIENT_MAX_AICS_INST=1 CONFIG_BT_MICS_CLIENT_MAX_AICS_INST=1
# Coordinated Set Identification
CONFIG_BT_CSIS=y