diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 736c40c11c5..061ad7f33f8 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -187,7 +187,10 @@ Protocols Bluetooth® LE ------------- -|no_changes_yet_note| +* Added the :c:func:`bt_nrf_conn_set_ltk` API. + + This API allows you to set a custom Long Term Key (LTK) for a connection. + You can use it when two devices have a shared proprietary method for obtaining an LTK. Bluetooth Mesh -------------- diff --git a/include/bluetooth/nrf/host_extensions.h b/include/bluetooth/nrf/host_extensions.h index 5b1312003b1..19aea198edb 100644 --- a/include/bluetooth/nrf/host_extensions.h +++ b/include/bluetooth/nrf/host_extensions.h @@ -16,6 +16,8 @@ #include #include +#include +#include /** @brief Set remote (peer) transmit power. * @@ -116,6 +118,61 @@ int bt_conn_set_power_control_request_params(struct bt_conn_set_pcr_params *para */ int bt_nrf_host_extension_reduce_initator_aux_channel_priority(bool reduce); +/** @brief Bluetooth Long Term Key (LTK) + * + * A Bluetooth LTK in little-endian order. The bytes in this structure + * are in the same order as in HCI commands. + */ +struct bt_nrf_ltk { + uint8_t val[16]; +}; + +/** + * @brief Set the LTK to be used when encryption is triggered. + * + * This API does not itself enable encryption. Instead, the LTK is set + * to be used when encryption is triggered by other means. Peripherals + * should wait for the central to trigger encryption, while centrals + * should follow up with a call to @c bt_conn_set_security(). + * + * This API may be used in a @c conn_cb.connected callback. It can also + * be called later at any time before encryption is triggered. In the + * connected callback, encryption is guaranteed not to have been + * triggered yet. + * + * This API is intended only as a mechanism for use between devices that + * both implement some particular proprietary method for deriving or + * provisioning a safe shared LTK out-of-band. It is not intended to be + * used on generic connections without knowledge that the peer device + * also implements the same proprietary method. + * + * The parameter @p authenticated is used to indicate whether the LTK is + * considered authenticated by the GAP layer. If true, the connection + * will get BT_SECURITY_L4 security level, otherwise it will get + * BT_SECURITY_L2 security level. Access to Bluetooth services is + * controlled by this security level. The user of this API must consider + * the security implications of this. + * + * It is highly recommended that the LTK is authenticated, and that + * unauthenticated LTKs are not trusted. + * + * Requires @c CONFIG_BT_NRF_CONN_SET_LTK to be enabled. + * + * @param conn Connection object. + * @param ltk LTK to set. + * @param authenticated Whether GAP will consider the connection to be + * authenticated. + * + * @retval -EALREADY The LTK has already been set using this API, a bond + * exists, a pairing is in progress or has already been completed. + * Please unpair first. + * @retval -ENOMEM Could not allocate Host memory. Increase + * @c CONFIG_BT_MAX_PAIRED. + * @retval -EINVAL Null pointer in arguments. + * @retval 0 Success. + */ +int bt_nrf_conn_set_ltk(struct bt_conn *conn, const struct bt_nrf_ltk *ltk, bool authenticated); + /** * @} */ diff --git a/subsys/bluetooth/host_extensions/CMakeLists.txt b/subsys/bluetooth/host_extensions/CMakeLists.txt index a3ecea60da3..d0681ab0a22 100644 --- a/subsys/bluetooth/host_extensions/CMakeLists.txt +++ b/subsys/bluetooth/host_extensions/CMakeLists.txt @@ -6,3 +6,4 @@ zephyr_sources(host_extensions.c) zephyr_sources_ifdef(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB radio_notification_conn_cb.c) +zephyr_sources_ifdef(CONFIG_BT_NRF_CONN_SET_LTK custom_ltk.c) diff --git a/subsys/bluetooth/host_extensions/Kconfig b/subsys/bluetooth/host_extensions/Kconfig index 4bcb3b28078..4ab6e54f495 100644 --- a/subsys/bluetooth/host_extensions/Kconfig +++ b/subsys/bluetooth/host_extensions/Kconfig @@ -16,3 +16,9 @@ config BT_RADIO_NOTIFICATION_CONN_CB connection event starts. This feature can be used to synchronize data sampling with on-air data transmission. + +config BT_NRF_CONN_SET_LTK + bool "Custom LTK support" + depends on BT_SMP + help + Enables the bt_nrf_conn_set_ltk() API. diff --git a/subsys/bluetooth/host_extensions/custom_ltk.c b/subsys/bluetooth/host_extensions/custom_ltk.c new file mode 100644 index 00000000000..a71b4913d7f --- /dev/null +++ b/subsys/bluetooth/host_extensions/custom_ltk.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* + * This file implements an NCS extension to Zephyr that allows the + * application to set a custom LTK from a vendor-defined source for a + * connection. This is a provisional feature that will likely be + * replaced by a standard Bluetooth feature in a future version of the + * specification. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_nrf_conn_set_ltk, CONFIG_BT_KEYS_LOG_LEVEL); + +/* These are Zephyr Host internal headers. These are not public headers, + * instead we use a path relative to `zephyr/include` and escape up one + * directory to get to the root of the Zephyr project. + */ +#include "../subsys/bluetooth/host/keys.h" +#include "../subsys/bluetooth/host/conn_internal.h" + +/* This enum extends the enum used for `bt_keys.flags`. + */ +enum { + NRF_BT_KEYS_TRANSIENT = BIT(7), +}; + +int bt_nrf_conn_set_ltk(struct bt_conn *conn, const struct bt_nrf_ltk *ltk, bool authenticated) +{ + struct bt_keys *keys; + + if (conn == NULL || ltk == NULL) { + return -EINVAL; + } + + /* This is a crude but decent prevention of any race conditions with + * smp.c. + * + * It should prevent a race with smp_pairing_req(), + * smp_security_request() and bt_smp_start_security(), which will all + * allocate keys early and cause the following code to find a key and + * return -EALREADY or conversely find the LTK we set here and use it as + * if it's from a bond. + */ + k_sched_lock(); + + /* If we find keys for this address it means a pairing or encryption may + * already be under way, or there is a prior bond for this address. We + * don't allow setting the LTK in this case. This is to avoid race + * conditions with unintended security consequences. + * + * Even if the keys were created with this API, we might still be racing + * with an encryption process. To avoid this, we treat setting the LTK + * as creating a transient bond, and in effect don't allow setting the + * LTK multiple times for the same connection. + */ + keys = bt_keys_find(BT_KEYS_ALL, conn->id, &conn->le.dst); + if (keys) { + char str[BT_ADDR_LE_STR_LEN]; + + k_sched_unlock(); + + bt_addr_le_to_str(&conn->le.dst, str, sizeof(str)); + LOG_ERR("Cannot safely set LTK for %s. A bt_keys already exists. Please unpair " + "first.", + str); + + return -EALREADY; + } + + /* We are using "get" variant here so it allocates. + */ + keys = bt_keys_get_addr(conn->id, &conn->le.dst); + if (!keys) { + char str[BT_ADDR_LE_STR_LEN]; + + k_sched_unlock(); + + bt_addr_le_to_str(&conn->le.dst, str, sizeof(str)); + LOG_ERR("Failed to allocate bt_keys for %s.", str); + + return -ENOMEM; + } + + conn->le.keys = keys; + + /* Use the LESC LTK type, which is symmetric for central and peripheral. + */ + keys->keys = BT_KEYS_LTK_P256; + + memcpy(keys->ltk.val, ltk->val, sizeof(ltk->val)); + keys->enc_size = sizeof(ltk->val); + + keys->flags = 0; + keys->flags |= BT_KEYS_SC; + keys->flags |= NRF_BT_KEYS_TRANSIENT; + + if (authenticated) { + keys->flags |= BT_KEYS_AUTHENTICATED; + } + + k_sched_unlock(); + + return 0; +} + +static void _on_disconnect(struct bt_conn *conn, uint8_t reason) +{ + struct bt_keys *keys = conn->le.keys; + + ARG_UNUSED(reason); + + /* Completely deallocate the keys entry to prevent leaking keys_pool + * entries. `bt_smp_disconnected` does that for paired-not-bonded + * bt_keys, but it recognizes those by finding a zero in + * `bt_keys.keys`. But our `bt_keys` do have an LTK flag. So we need to + * do the job ourselves. + */ + if (keys && keys->flags & NRF_BT_KEYS_TRANSIENT) { + conn->le.keys = NULL; + bt_keys_clear(keys); + } +} + +static void _on_security_changed(struct bt_conn *conn, bt_security_t level, + enum bt_security_err err) +{ + struct bt_keys *keys = conn->le.keys; + + ARG_UNUSED(level); + ARG_UNUSED(err); + + /* Clear the LTK so that this bt_keys looks like it belongs to a + * paired-not-bonded device, so the bt_keys is not saved to flash. + * Clearing the key is also good for security. + * + * We do not deallocate here, so that `bt_conn_get_info` can pick up + * info like key size and SC flag. + * + * If the encryption has failed, for example with + * BT_SECURITY_ERR_PIN_OR_KEY_MISSING, we clear it anyway, since it + * obviously doesn't work. It shouldn't happen if the application + * does everything correctly. We don't deallocate the keys entry + * because we don't want to risk any race conditions, just in case. + * If users want to retry (potentially with a different LTK), they + * have to disconnect and reconnect to do so. + */ + if (keys && keys->flags & NRF_BT_KEYS_TRANSIENT) { + keys->keys = 0; + keys->ltk = (struct bt_ltk){}; + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .disconnected = _on_disconnect, + .security_changed = _on_security_changed, +};