I am working in NCS v2.7.0 for nRF52833 DevKits, and trying to set up a secure BLE connection to allow remote control of the device over GATT.
My devices are headless IOT devices, so there is no possibility for a true OOB data pathway and no user nearby to provide feedback, so none of the basic examples quite work. My understanding is that out of the box I would need to either use legacy static OOB which exposes me to a lack of forward secrecy, or use LESC with a static passkey which exposes me to brute force attacks.
What I would like to do is provide a fixed 16-byte random seed to an LE SC OOB data structure (bt_le_oob_sc_data.r), and have the device compute the confirm value based on the current public key. This would let me leverage ephemeral DH keys from LESC ensuring forward secrecy, and a 128-bit shared secret is a lot harder to brute force then a 6-digit static pin. I understand that true OOB is much better, but the actual alternatives available to me are much worse.
First, before I get into my mess, is there a native way to accomplish what I'm trying to do that I'm missing?
If not, what I did was hack a hook into 'smp.c' to allow retrieval of the 'sc_public_key' value:
const uint8_t *bt_smp_get_sc_public_key(void) { return sc_public_key; }
Then in my application, I added the following code to my initiator (central device) to re-calculate the confirm value on each request:
#include "crypto/bt_crypto.h" // Hacked into the SDK smp.c to fetch the static 'sc_public_key' value extern const uint8_t *bt_smp_get_sc_public_key(void); static struct bt_le_oob_sc_data oob_local; static const uint8_t static_r[16] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE }; static void oob_data_request(struct bt_conn *conn, struct bt_conn_oob_info *info) { int err; if (info->type != BT_CONN_OOB_LE_SC) { printk("OOB type not LE SC, rejected\n"); return; } printk("Central: OOB data requested\n"); memcpy(oob_local.r, static_r, 16); err = bt_crypto_f4(bt_smp_get_sc_public_key(), bt_smp_get_sc_public_key(), &oob_local.r, 0, &oob_local.c); if (err) { printk("Error generating OOB confirm value: %d\n", err); return; } err = bt_le_oob_set_sc_data(conn, &oob_local, NULL); if (err) { printk("Error while setting OOB data: %d\n", err); } }
I have basically the same thing added to the peripheral device, except argument 4 into 'bt_crypto_f4' is set to 1 instead of 0 to reflect the different role.
I think this should work, but my pairing request always end with a status 0xB (BT_SMP_ERR_DHKEY_CHECK_FAILED). Am I missing something, or is what I'm trying to do just not possible.
Thanks,
Jeremy