-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathnrf_ble_lesc.c
More file actions
390 lines (317 loc) · 11.6 KB
/
nrf_ble_lesc.c
File metadata and controls
390 lines (317 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/*
* Copyright (c) 2018-2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include <stdbool.h>
#include <stdint.h>
#include <ble_gap.h>
#include <nrf_error.h>
#include <bm/softdevice_handler/nrf_sdh_ble.h>
#include <psa/crypto.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/toolchain.h>
#include <bm/bluetooth/peer_manager/nrf_ble_lesc.h>
LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL);
/** @brief Descriptor of the peer public key. */
struct lesc_peer_pub_key {
/** @brief Peer public key. Stored in little-endian. */
uint8_t value[BLE_GAP_LESC_P256_PK_LEN];
/** @brief Peer connection handle. */
uint16_t conn_handle;
/** @brief Flag indicating that the public key has been requested to compute DH key. */
bool is_requested;
};
/**
* @brief The maximum number of peripheral and central connections combined.
* This value is based on what the SoftDevice handler module uses.
*/
#define NRF_BLE_LESC_LINK_COUNT CONFIG_NRF_SDH_BLE_TOTAL_LINK_COUNT
/** LESC ECC Public Key. */
__aligned(4) static ble_gap_lesc_p256_pk_t lesc_public_key;
/** LESC ECC DH Key. */
__aligned(4) static ble_gap_lesc_dhkey_t lesc_dh_key;
/** Flag indicating that the module encountered an internal error. */
static bool ble_lesc_internal_error;
/** Flag indicating that the local ECDH key pair was generated. */
static bool keypair_generated;
/** ID of ECC private/public key pair. */
static psa_key_id_t keypair_id;
/** Array of pointers to peer public keys, used for LESC DH generation. */
static struct lesc_peer_pub_key peer_keys[NRF_BLE_LESC_LINK_COUNT];
static bool lesc_oobd_own_generated;
/** LESC OOB data used in LESC OOB pairing mode. */
static ble_gap_lesc_oob_data_t ble_lesc_oobd_own;
static nrf_ble_lesc_peer_oob_data_handler lesc_oobd_peer_handler;
#define ECC_PUB_KEY_UNCOMPRESSED_FORMAT_MARKER 0x04
#define ECC_PUB_KEY_EXPORT_SIZE \
PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), 256)
#define ECC_PRIV_KEY_EXPORT_SIZE \
PSA_EXPORT_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), 256)
#define COORD_SIZE (BLE_GAP_LESC_P256_PK_LEN / 2)
/* Convert an ECC (secp256r1) public key from between big-endian and little-endian.
* The two coordinates are converted individually.
*/
static void ecc_public_key_byte_order_invert(const uint8_t *raw_key_in, uint8_t *raw_key_out)
{
sys_memcpy_swap(raw_key_out, raw_key_in, COORD_SIZE);
sys_memcpy_swap(&raw_key_out[COORD_SIZE], &raw_key_in[COORD_SIZE], COORD_SIZE);
}
uint32_t nrf_ble_lesc_init(void)
{
psa_status_t status;
memset((void *)peer_keys, 0, sizeof(peer_keys));
/* Ensure that psa_crypto has been initialized. */
status = psa_crypto_init();
if (status != PSA_SUCCESS) {
LOG_ERR("psa_crypto_init() returned status %d", status);
return NRF_ERROR_INTERNAL;
}
LOG_DBG("Initialized nrf_ble_lesc.");
/* Reset module state. */
ble_lesc_internal_error = false;
keypair_generated = false;
/* Generate ECC key pair. Only one key pair is automatically generated by this module. */
return nrf_ble_lesc_keypair_generate();
}
uint32_t nrf_ble_lesc_keypair_generate(void)
{
psa_status_t status;
uint8_t pub_key[ECC_PUB_KEY_EXPORT_SIZE];
size_t pub_key_len = 0;
/* Check if any DH computation is pending */
for (uint16_t i = 0; i < ARRAY_SIZE(peer_keys); i++) {
if (peer_keys[i].is_requested) {
return NRF_ERROR_BUSY;
}
}
/* Update flag to indicate that there is no valid private key. */
keypair_generated = false;
lesc_oobd_own_generated = false;
/* Destroy the previous key pair (if any), to free up memory. */
status = psa_destroy_key(keypair_id);
if (status != PSA_SUCCESS && status != PSA_ERROR_INVALID_HANDLE) {
LOG_ERR("psa_destroy_key returned status %d", status);
} else {
keypair_id = 0;
}
LOG_DBG("Generating ECC key pair");
psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT;
#if defined(CONFIG_PM_LESC_PRIVATE_KEY_EXPORT)
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE | PSA_KEY_USAGE_EXPORT);
#else
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE);
#endif
psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE);
psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDH);
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&key_attributes, 256);
status = psa_generate_key(&key_attributes, &keypair_id);
if (status != PSA_SUCCESS) {
LOG_ERR("psa_generate_key() returned status %d", status);
return NRF_ERROR_INTERNAL;
}
/* Export the raw representation of the public key. */
status = psa_export_public_key(keypair_id, pub_key, sizeof(pub_key), &pub_key_len);
if (status != PSA_SUCCESS) {
LOG_ERR("psa_export_public_key() returned status %d", status);
return NRF_ERROR_INTERNAL;
}
#if defined(CONFIG_PM_LESC_PRIVATE_KEY_EXPORT)
uint8_t priv_key[ECC_PRIV_KEY_EXPORT_SIZE];
size_t priv_key_len = 0;
LOG_WRN("CONFIG_PM_LESC_PRIVATE_KEY_EXPORT is not to be used in production!");
status = psa_export_key(keypair_id, priv_key, sizeof(priv_key), &priv_key_len);
if (status != PSA_SUCCESS) {
LOG_ERR("psa_export_key() returned status %d", status);
} else {
printk("PRIV KEY: 0x");
for (int i = 0; i < sizeof(priv_key); i++) {
printk("%02x", priv_key[i]);
}
printk("\n\n");
}
#endif
/* Convert from big-endian to little-endian.
* Drop the first byte indicating the serialization format.
*/
ecc_public_key_byte_order_invert(&pub_key[1], lesc_public_key.pk);
/* Set the flag to indicate that there is a valid ECDH key pair generated. */
keypair_generated = true;
return NRF_SUCCESS;
}
uint32_t nrf_ble_lesc_own_oob_data_generate(void)
{
uint32_t nrf_err = NRF_ERROR_INVALID_STATE;
lesc_oobd_own_generated = false;
if (keypair_generated) {
nrf_err = sd_ble_gap_lesc_oob_data_get(BLE_CONN_HANDLE_INVALID, &lesc_public_key,
&ble_lesc_oobd_own);
if (nrf_err == NRF_SUCCESS) {
lesc_oobd_own_generated = true;
}
}
return nrf_err;
}
ble_gap_lesc_p256_pk_t *nrf_ble_lesc_public_key_get(void)
{
ble_gap_lesc_p256_pk_t *lesc_pk = NULL;
if (keypair_generated) {
lesc_pk = &lesc_public_key;
} else {
LOG_ERR("Trying to access LESC public key that has not been generated yet.");
}
return lesc_pk;
}
ble_gap_lesc_oob_data_t *nrf_ble_lesc_own_oob_data_get(void)
{
ble_gap_lesc_oob_data_t *lesc_oobd_own = NULL;
if (lesc_oobd_own_generated) {
lesc_oobd_own = &ble_lesc_oobd_own;
} else {
LOG_ERR("Trying to access LESC OOB data that have not been generated yet.");
}
return lesc_oobd_own;
}
void nrf_ble_lesc_peer_oob_data_handler_set(nrf_ble_lesc_peer_oob_data_handler handler)
{
lesc_oobd_peer_handler = handler;
}
/**
* @brief Function for calculating a DH key and responding to the DH key request.
*
* @param[in] peer_public_key ECC peer public key, used to compute shared secret.
*
* @retval NRF_SUCCESS If the operation was successful.
* @retval NRF_ERROR_INTERNAL If @ref psa_raw_key_agreement, or @ref psa_generate_random failed.
* @return Other error codes might be returned by @ref sd_ble_gap_lesc_dhkey_reply.
* functions.
*/
static uint32_t compute_and_give_dhkey(struct lesc_peer_pub_key *peer_public_key)
{
psa_status_t status = PSA_ERROR_BAD_STATE;
uint8_t *shared_secret = lesc_dh_key.key;
size_t shared_secret_size;
uint8_t pub_key[ECC_PUB_KEY_EXPORT_SIZE];
ble_gap_lesc_dhkey_t *p_dh_key = NULL;
uint8_t sec_status = BLE_GAP_SEC_STATUS_DHKEY_FAILURE;
/* Check if there is a valid generated and set a local ECDH public key. */
if (!keypair_generated) {
return NRF_ERROR_INTERNAL;
}
/* Don't allow to pair with remote peer which uses the same public key.
* Compare only X cordinate of the public key, bytes from 0 to 31.
*/
if (memcmp(lesc_public_key.pk, peer_public_key->value, BLE_GAP_LESC_P256_PK_LEN / 2) == 0) {
LOG_WRN("Remote peer is using identical public key.");
} else {
/* Add the uncompressed format marker. */
pub_key[0] = ECC_PUB_KEY_UNCOMPRESSED_FORMAT_MARKER;
/* Convert from little-endian to big-endian. */
ecc_public_key_byte_order_invert(peer_public_key->value, &pub_key[1]);
status = psa_raw_key_agreement(PSA_ALG_ECDH, keypair_id,
pub_key, sizeof(pub_key),
shared_secret, BLE_GAP_LESC_DHKEY_LEN, &shared_secret_size);
}
if ((status == PSA_SUCCESS) && (shared_secret_size == BLE_GAP_LESC_DHKEY_LEN)) {
/* Convert secret from big-endian to little-endian. */
sys_mem_swap(shared_secret, BLE_GAP_LESC_DHKEY_LEN);
p_dh_key = &lesc_dh_key;
sec_status = BLE_GAP_SEC_STATUS_SUCCESS;
} else {
LOG_ERR("psa_raw_key_agreement() returned status %d", status);
}
LOG_INF("Calling sd_ble_gap_lesc_dhkey_reply(sec_status: %#x) on conn_handle: %d",
sec_status, peer_public_key->conn_handle);
return sd_ble_gap_lesc_dhkey_reply(peer_public_key->conn_handle, sec_status, p_dh_key);
}
uint32_t nrf_ble_lesc_request_handler(void)
{
uint32_t nrf_err;
/* If the LESC module is in an invalid state, a restart is required. */
if (ble_lesc_internal_error) {
return NRF_ERROR_INTERNAL;
}
for (uint16_t i = 0; i < NRF_BLE_LESC_LINK_COUNT; i++) {
if (peer_keys[i].is_requested) {
nrf_err = compute_and_give_dhkey(&peer_keys[i]);
peer_keys[i].is_requested = false;
if (nrf_err) {
return nrf_err;
}
}
}
return NRF_SUCCESS;
}
/**
* @brief Function for handling a DH key request event.
*
* @param[in] conn_handle Connection handle.
* @param[in] idx Data index assigned to the connection handle.
* @param[in] dhkey_request DH key request descriptor.
*/
static void on_dhkey_request(uint16_t conn_handle, int idx,
const ble_gap_evt_lesc_dhkey_request_t *dhkey_request)
{
const uint8_t *const public_raw = dhkey_request->p_pk_peer->pk;
memcpy(peer_keys[idx].value, public_raw, BLE_GAP_LESC_P256_PK_LEN);
peer_keys[idx].conn_handle = conn_handle;
peer_keys[idx].is_requested = true;
}
/**
* @brief Function for setting LESC OOB data.
*
* @param[in] conn_handle Connection handle.
*
* @retval NRF_SUCCESS If the operation was successful.
* @return Other error codes might be returned by @ref sd_ble_gap_lesc_oob_data_set.
*/
static uint32_t lesc_oob_data_set(uint16_t conn_handle)
{
ble_gap_lesc_oob_data_t *lesc_oobd_own;
ble_gap_lesc_oob_data_t *lesc_oobd_peer;
lesc_oobd_own = (lesc_oobd_own_generated) ? &ble_lesc_oobd_own : NULL;
lesc_oobd_peer =
(lesc_oobd_peer_handler != NULL) ? lesc_oobd_peer_handler(conn_handle) : NULL;
return sd_ble_gap_lesc_oob_data_set(conn_handle, lesc_oobd_own, lesc_oobd_peer);
}
void nrf_ble_lesc_on_ble_evt(const ble_evt_t *ble_evt)
{
__ASSERT(ble_evt, "ble_evt is NULL");
uint32_t nrf_err = NRF_SUCCESS;
const uint16_t conn_handle = ble_evt->evt.gap_evt.conn_handle;
const int idx = nrf_sdh_ble_idx_get(conn_handle);
__ASSERT(idx >= 0, "Invalid idx %d for conn_handle %#x, evt_id %#x",
idx, conn_handle, ble_evt->header.evt_id);
switch (ble_evt->header.evt_id) {
case BLE_GAP_EVT_DISCONNECTED:
peer_keys[idx].is_requested = false;
break;
case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
LOG_DBG("BLE_GAP_EVT_LESC_DHKEY_REQUEST");
if (ble_evt->evt.gap_evt.params.lesc_dhkey_request.oobd_req) {
nrf_err = lesc_oob_data_set(conn_handle);
if (nrf_err) {
LOG_ERR("sd_ble_gap_lesc_oob_data_set() returned error %#x.",
nrf_err);
ble_lesc_internal_error = true;
}
}
on_dhkey_request(conn_handle, idx, &ble_evt->evt.gap_evt.params.lesc_dhkey_request);
break;
#if defined(CONFIG_PM_LESC_GENERATE_NEW_KEYS)
case BLE_GAP_EVT_AUTH_STATUS:
/* Generate new pairing keys. */
nrf_err = nrf_ble_lesc_keypair_generate();
if (nrf_err) {
ble_lesc_internal_error = true;
}
break;
#endif /* CONFIG_PM_LESC_GENERATE_NEW_KEYS */
default:
break;
}
}