LESC Static OOB?

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

Parents
  • So I've done some more digging, and think I'm missing something more basic. I've removed my own attempts to inject a static 'r' value and instead just set CONFIG_BT_TESTING and CONFIG_BT_OOB_DATA_FIXED to 'y' in 'prj.conf'. This appears to effectively do what I was trying to do, only with a fixed-in-the-SDK static value ... and is still failing the same as before.

    The new simplified code implemented on both central and peripheral now is:

    static struct bt_le_oob oob_local_full;
    
    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("OOB data requested\n");
    	
    	err = bt_le_oob_set_sc_data(conn, &oob_local_full.le_sc_data, NULL);
    	if (err) {
    		printk("Error while setting OOB data: %d\n", err);
    	}
    }
    
    
    int main(void)
    {
    	...
    	
    	bt_le_oob_get_local(0, &oob_local_full);
    
    	...
    }

    With debugging I can confirm this leads to identical 'r' values being seeded onto both devices and being supplied in smp->oodb_local, but they are still returning status 0xB. Any help would be appreciated.

    Thanks,
    Jeremy

Reply
  • So I've done some more digging, and think I'm missing something more basic. I've removed my own attempts to inject a static 'r' value and instead just set CONFIG_BT_TESTING and CONFIG_BT_OOB_DATA_FIXED to 'y' in 'prj.conf'. This appears to effectively do what I was trying to do, only with a fixed-in-the-SDK static value ... and is still failing the same as before.

    The new simplified code implemented on both central and peripheral now is:

    static struct bt_le_oob oob_local_full;
    
    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("OOB data requested\n");
    	
    	err = bt_le_oob_set_sc_data(conn, &oob_local_full.le_sc_data, NULL);
    	if (err) {
    		printk("Error while setting OOB data: %d\n", err);
    	}
    }
    
    
    int main(void)
    {
    	...
    	
    	bt_le_oob_get_local(0, &oob_local_full);
    
    	...
    }

    With debugging I can confirm this leads to identical 'r' values being seeded onto both devices and being supplied in smp->oodb_local, but they are still returning status 0xB. Any help would be appreciated.

    Thanks,
    Jeremy

Children
No Data
Related