Skip to content

Commit 9d53130

Browse files
committed
Bluetooth: Add custom LTK support
This adds the bt_nrf_conn_set_ltk() API, which allows the user to set a custom LTK for a connection. Signed-off-by: Aleksander Wasaznik <[email protected]>
1 parent 00c893a commit 9d53130

File tree

5 files changed

+240
-1
lines changed

5 files changed

+240
-1
lines changed

doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ Protocols
187187
Bluetooth® LE
188188
-------------
189189

190-
|no_changes_yet_note|
190+
* Added the :c:func:`bt_nrf_conn_set_ltk` API.
191+
192+
This API allows you to set a custom Long Term Key (LTK) for a connection.
193+
You can use it when two devices have a shared proprietary method for obtaining an LTK.
191194

192195
Bluetooth Mesh
193196
--------------

include/bluetooth/nrf/host_extensions.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#include <stdbool.h>
1818
#include <stdint.h>
19+
#include <zephyr/bluetooth/bluetooth.h>
20+
#include <zephyr/bluetooth/conn.h>
1921

2022
/** @brief Set remote (peer) transmit power.
2123
*
@@ -116,6 +118,61 @@ int bt_conn_set_power_control_request_params(struct bt_conn_set_pcr_params *para
116118
*/
117119
int bt_nrf_host_extension_reduce_initator_aux_channel_priority(bool reduce);
118120

121+
/** @brief Bluetooth Long Term Key (LTK)
122+
*
123+
* A Bluetooth LTK in little-endian order. The bytes in this structure
124+
* are in the same order as in HCI commands.
125+
*/
126+
struct bt_nrf_ltk {
127+
uint8_t val[16];
128+
};
129+
130+
/**
131+
* @brief Set the LTK to be used when encryption is triggered.
132+
*
133+
* This API does not itself enable encryption. Instead, the LTK is set
134+
* to be used when encryption is triggered by other means. Peripherals
135+
* should wait for the central to trigger encryption, while centrals
136+
* should follow up with a call to @c bt_conn_set_security().
137+
*
138+
* This API may be used in a @c conn_cb.connected callback. It can also
139+
* be called later at any time before encryption is triggered. In the
140+
* connected callback, encryption is guaranteed not to have been
141+
* triggered yet.
142+
*
143+
* This API is intended only as a mechanism for use between devices that
144+
* both implement some particular proprietary method for deriving or
145+
* provisioning a safe shared LTK out-of-band. It is not intended to be
146+
* used on generic connections without knowledge that the peer device
147+
* also implements the same proprietary method.
148+
*
149+
* The parameter @p authenticated is used to indicate whether the LTK is
150+
* considered authenticated by the GAP layer. If true, the connection
151+
* will get BT_SECURITY_L4 security level, otherwise it will get
152+
* BT_SECURITY_L2 security level. Access to Bluetooth services is
153+
* controlled by this security level. The user of this API must consider
154+
* the security implications of this.
155+
*
156+
* It is highly recommended that the LTK is authenticated, and that
157+
* unauthenticated LTKs are not trusted.
158+
*
159+
* Requires @c CONFIG_BT_NRF_CONN_SET_LTK to be enabled.
160+
*
161+
* @param conn Connection object.
162+
* @param ltk LTK to set.
163+
* @param authenticated Whether GAP will consider the connection to be
164+
* authenticated.
165+
*
166+
* @retval -EALREADY The LTK has already been set using this API, a bond
167+
* exists, a pairing is in progress or has already been completed.
168+
* Please unpair first.
169+
* @retval -ENOMEM Could not allocate Host memory. Increase
170+
* @c CONFIG_BT_MAX_PAIRED.
171+
* @retval -EINVAL Null pointer in arguments.
172+
* @retval 0 Success.
173+
*/
174+
int bt_nrf_conn_set_ltk(struct bt_conn *conn, const struct bt_nrf_ltk *ltk, bool authenticated);
175+
119176
/**
120177
* @}
121178
*/

subsys/bluetooth/host_extensions/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66

77
zephyr_sources(host_extensions.c)
88
zephyr_sources_ifdef(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB radio_notification_conn_cb.c)
9+
zephyr_sources_ifdef(CONFIG_BT_NRF_CONN_SET_LTK custom_ltk.c)

subsys/bluetooth/host_extensions/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ config BT_RADIO_NOTIFICATION_CONN_CB
1616
connection event starts.
1717
This feature can be used to synchronize data sampling
1818
with on-air data transmission.
19+
20+
config BT_NRF_CONN_SET_LTK
21+
bool "Custom LTK support"
22+
depends on BT_SMP
23+
help
24+
Enables the bt_nrf_conn_set_ltk() API.
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
/*
8+
* This file implements an NCS extension to Zephyr that allows the
9+
* application to set a custom LTK from a vendor-defined source for a
10+
* connection. This is a provisional feature that will likely be
11+
* replaced by a standard Bluetooth feature in a future version of the
12+
* specification.
13+
*/
14+
15+
#include <autoconf.h>
16+
#include <bluetooth/nrf/host_extensions.h>
17+
#include <errno.h>
18+
#include <stdint.h>
19+
#include <string.h>
20+
#include <zephyr/bluetooth/addr.h>
21+
#include <zephyr/bluetooth/conn.h>
22+
#include <zephyr/kernel.h>
23+
#include <zephyr/logging/log.h>
24+
#include <zephyr/sys/check.h>
25+
#include <zephyr/sys/util_macro.h>
26+
#include <zephyr/toolchain.h>
27+
28+
LOG_MODULE_REGISTER(bt_nrf_conn_set_ltk, CONFIG_BT_KEYS_LOG_LEVEL);
29+
30+
/* These are Zephyr Host internal headers. These are not public headers,
31+
* instead we use a path relative to `zephyr/include` and escape up one
32+
* directory to get to the root of the Zephyr project.
33+
*/
34+
#include "../subsys/bluetooth/host/keys.h"
35+
#include "../subsys/bluetooth/host/conn_internal.h"
36+
37+
/* This enum extends the enum used for `bt_keys.flags`.
38+
*/
39+
enum {
40+
NRF_BT_KEYS_TRANSIENT = BIT(7),
41+
};
42+
43+
int bt_nrf_conn_set_ltk(struct bt_conn *conn, const struct bt_nrf_ltk *ltk, bool authenticated)
44+
{
45+
struct bt_keys *keys;
46+
47+
if (conn == NULL || ltk == NULL) {
48+
return -EINVAL;
49+
}
50+
51+
/* This is a crude but decent prevention of any race conditions with
52+
* smp.c.
53+
*
54+
* It should prevent a race with smp_pairing_req(),
55+
* smp_security_request() and bt_smp_start_security(), which will all
56+
* allocate keys early and cause the following code to find a key and
57+
* return -EALREADY or conversely find the LTK we set here and use it as
58+
* if it's from a bond.
59+
*/
60+
k_sched_lock();
61+
62+
/* If we find keys for this address it means a pairing or encryption may
63+
* already be under way, or there is a prior bond for this address. We
64+
* don't allow setting the LTK in this case. This is to avoid race
65+
* conditions with unintended security consequences.
66+
*
67+
* Even if the keys were created with this API, we might still be racing
68+
* with an encryption process. To avoid this, we treat setting the LTK
69+
* as creating a transient bond, and in effect don't allow setting the
70+
* LTK multiple times for the same connection.
71+
*/
72+
keys = bt_keys_find(BT_KEYS_ALL, conn->id, &conn->le.dst);
73+
if (keys) {
74+
char str[BT_ADDR_LE_STR_LEN];
75+
76+
k_sched_unlock();
77+
78+
bt_addr_le_to_str(&conn->le.dst, str, sizeof(str));
79+
LOG_ERR("Cannot safely set LTK for %s. A bt_keys already exists. Please unpair "
80+
"first.",
81+
str);
82+
83+
return -EALREADY;
84+
}
85+
86+
/* We are using "get" variant here so it allocates.
87+
*/
88+
keys = bt_keys_get_addr(conn->id, &conn->le.dst);
89+
if (!keys) {
90+
char str[BT_ADDR_LE_STR_LEN];
91+
92+
k_sched_unlock();
93+
94+
bt_addr_le_to_str(&conn->le.dst, str, sizeof(str));
95+
LOG_ERR("Failed to allocate bt_keys for %s.", str);
96+
97+
return -ENOMEM;
98+
}
99+
100+
conn->le.keys = keys;
101+
102+
/* Use the LESC LTK type, which is symmetric for central and peripheral.
103+
*/
104+
keys->keys = BT_KEYS_LTK_P256;
105+
106+
memcpy(keys->ltk.val, ltk->val, sizeof(ltk->val));
107+
keys->enc_size = sizeof(ltk->val);
108+
109+
keys->flags = 0;
110+
keys->flags |= BT_KEYS_SC;
111+
keys->flags |= NRF_BT_KEYS_TRANSIENT;
112+
113+
if (authenticated) {
114+
keys->flags |= BT_KEYS_AUTHENTICATED;
115+
}
116+
117+
k_sched_unlock();
118+
119+
return 0;
120+
}
121+
122+
static void _on_disconnect(struct bt_conn *conn, uint8_t reason)
123+
{
124+
struct bt_keys *keys = conn->le.keys;
125+
126+
ARG_UNUSED(reason);
127+
128+
/* Completely deallocate the keys entry to prevent leaking keys_pool
129+
* entries. `bt_smp_disconnected` does that for paired-not-bonded
130+
* bt_keys, but it recognizes those by finding a zero in
131+
* `bt_keys.keys`. But our `bt_keys` do have an LTK flag. So we need to
132+
* do the job ourselves.
133+
*/
134+
if (keys && keys->flags & NRF_BT_KEYS_TRANSIENT) {
135+
conn->le.keys = NULL;
136+
bt_keys_clear(keys);
137+
}
138+
}
139+
140+
static void _on_security_changed(struct bt_conn *conn, bt_security_t level,
141+
enum bt_security_err err)
142+
{
143+
struct bt_keys *keys = conn->le.keys;
144+
145+
ARG_UNUSED(level);
146+
ARG_UNUSED(err);
147+
148+
/* Clear the LTK so that this bt_keys looks like it belongs to a
149+
* paired-not-bonded device, so the bt_keys is not saved to flash.
150+
* Clearing the key is also good for security.
151+
*
152+
* We do not deallocate here, so that `bt_conn_get_info` can pick up
153+
* info like key size and SC flag.
154+
*
155+
* If the encryption has failed, for example with
156+
* BT_SECURITY_ERR_PIN_OR_KEY_MISSING, we clear it anyway, since it
157+
* obviously doesn't work. It shouldn't happen if the application
158+
* does everything correctly. We don't deallocate the keys entry
159+
* because we don't want to risk any race conditions, just in case.
160+
* If users want to retry (potentially with a different LTK), they
161+
* have to disconnect and reconnect to do so.
162+
*/
163+
if (keys && keys->flags & NRF_BT_KEYS_TRANSIENT) {
164+
keys->keys = 0;
165+
keys->ltk = (struct bt_ltk){};
166+
}
167+
}
168+
169+
BT_CONN_CB_DEFINE(conn_callbacks) = {
170+
.disconnected = _on_disconnect,
171+
.security_changed = _on_security_changed,
172+
};

0 commit comments

Comments
 (0)