Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------
Expand Down
57 changes: 57 additions & 0 deletions include/bluetooth/nrf/host_extensions.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include <stdbool.h>
#include <stdint.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>

/** @brief Set remote (peer) transmit power.
*
Expand Down Expand Up @@ -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);

/**
* @}
*/
Expand Down
1 change: 1 addition & 0 deletions subsys/bluetooth/host_extensions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
6 changes: 6 additions & 0 deletions subsys/bluetooth/host_extensions/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
172 changes: 172 additions & 0 deletions subsys/bluetooth/host_extensions/custom_ltk.c
Original file line number Diff line number Diff line change
@@ -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 <autoconf.h>
#include <bluetooth/nrf/host_extensions.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/toolchain.h>

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),
};
Comment on lines +37 to +41
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The comment says "This enum extends the enum used for bt_keys.flags" but NRF_BT_KEYS_TRANSIENT is the only member defined. Consider clarifying that this is defining extension flags that augment the flags defined in Zephyr's internal headers, or rename the comment to be more precise (e.g., "Extension flag for bt_keys.flags").

Copilot uses AI. Check for mistakes.

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,
};