Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

pc-ble-driver-py - BLEGapSecStatus.dhkey_failure

Hi,

I'm using an NRF51 Dongle as a central to connect to a BLE peripheral using LE Secure passkey pairing. I've got to scanning / connecting successfully, I'm just stuck on the pairing.

It gets to the point where it sends the SMP Pairing Public Key but it's all 0's, so the peripheral rejects it with a DHKey Check Failed:

I've used the heart rate collector and a few examples around to get to this point, here's my pairing function:

if self.conn_handle is None:
            return False
            
kdist_own   = BLEGapSecKDist(enc  = 1,
                             id   = 1,
                             sign = 0,
                             link = 0)
                             
kdist_peer  = BLEGapSecKDist(enc  = 1,
                             id   = 1,
                             sign = 0,
                             link = 0)

sec_params  = BLEGapSecParams(bond          = True,
                              mitm          = True,
                              lesc          = True,
                              oob           = False,
                              keypress      = False,
                              io_caps       = BLEGapIOCaps.keyboard_display,
                              min_key_size  = 7,
                              max_key_size  = 16,
                              kdist_own     = kdist_own,
                              kdist_peer    = kdist_peer)

self.adapter.driver.ble_gap_authenticate(self.conn_handle, sec_params)

self.adapter.evt_sync[self.conn_handle].wait(BLEEvtID.gap_evt_sec_params_request)
self.adapter.driver.ble_gap_sec_params_reply(self.conn_handle,
                                             BLEGapSecStatus.success,
                                             sec_params = None)
                                
result = self.adapter.evt_sync[self.conn_handle].wait(BLEEvtID.gap_evt_auth_status)

if result['auth_status'] ==  BLEGapSecStatus.success:
    self.keyset = adapter.db_conns[self.conn_handle]._keyset
    return True        

return False

think the issue is around the keyset that's being used in the call to ble_gap_sec_params_reply but I can't figure out how to get the keys set to something non-zero.

I get an error in the debug logging after the gap_evt_auth_status comes back:

evt> auth_status conn(0)
 error_src(1)
 bonded(0)
 sm1_levels(<pc_ble_driver_py.lib.nrf_ble_driver_sd_api_v2.ble_gap_sec_levels_t; proxy of <Swig Object of type 'ble_gap_sec_levels_t *' at 0x000001AC59C56780> >)
 sm2_levels(<pc_ble_driver_py.lib.nrf_ble_driver_sd_api_v2.ble_gap_sec_levels_t; proxy of <Swig Object of type 'ble_gap_sec_levels_t *' at 0x000001AC59C564B0> >)
 kdist_own(enc(0) id(1) sign(0) link(0))
 kdist_peer(enc(0) id(0) sign(0) link(0))
 auth_status(BLEGapSecStatus.dhkey_failure)

Full code:

import sys
import time
import logging
from pc_ble_driver_py import config
config.__conn_ic_id__ = "NRF51"
from queue import Queue, Empty
from pc_ble_driver_py.observers import *
import pc_ble_driver_py.lib.nrf_ble_driver_sd_api_v2 as driver
import pc_ble_driver_py.ble_driver_types as util

TARGET_DEV_NAME = "BLE_DEVICE"
CFG_TAG = 1





class HRCollector(BLEDriverObserver, BLEAdapterObserver):
    def __init__(self, adapter):
        super(HRCollector, self).__init__()
        self.adapter = adapter
        self.conn_q = Queue()
        self.adapter.observer_register(self)
        self.adapter.driver.observer_register(self)
        self.adapter.default_mtu = 250
        self.keyset = None
        self.conn_handle = None

    def open(self):
        self.adapter.driver.open()
        self.adapter.driver.ble_enable(BLEEnableParams(
                                            vs_uuid_count=5,
                                            service_changed=0,
                                            periph_conn_count=0,
                                            central_conn_count=1,
                                            central_sec_count=1))

    def close(self):
        self.adapter.driver.close()

    def connect_and_discover(self):
        scan_duration = 60
        params = BLEGapScanParams(interval_ms=200, window_ms=150, timeout_s=scan_duration)
        self.adapter.driver.ble_gap_scan_start(scan_params=params)

        try:
            new_conn = self.conn_q.get(timeout=scan_duration)
            self.adapter.service_discovery(new_conn)

            return new_conn
        except Empty:
            print(f"No device advertising with name {TARGET_DEV_NAME} found.")
            return None


    def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params):
        print("New connection: {}".format(conn_handle))
        self.conn_q.put(conn_handle)

        self.conn_handle = conn_handle
    

    def on_gap_evt_auth_key_request(self, ble_driver, conn_handle, **kwargs):
        passkey = util.list_to_uint8_array([1, 1, 1, 1, 1, 1])

        print("passkey requested")

        driver.sd_ble_gap_auth_key_reply(
            ble_driver.rpc_adapter,
            conn_handle,
            kwargs['key_type'],
            passkey.cast(),
        ) 

    def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason):
        print("Disconnected: {} {}".format(conn_handle, reason))
        self.conn_handle = None

    def on_gap_evt_adv_report(self, ble_driver, conn_handle, peer_addr, rssi, adv_type, adv_data):
        if BLEAdvData.Types.short_local_name in adv_data.records:
            dev_name_list = adv_data.records[BLEAdvData.Types.short_local_name]

            dev_name = "".join(chr(e) for e in dev_name_list)
            address_string = "".join("{0:02X}".format(b) for b in peer_addr.addr)
            print("Received advertisment report, address: 0x{}, device_name: {}".format(address_string, dev_name))

            if dev_name == TARGET_DEV_NAME:
                self.adapter.connect(peer_addr, tag=CFG_TAG)


    def authenticate(self):
        if self.conn_handle is None:
            return False
            
        kdist_own   = BLEGapSecKDist(enc  = 1,
                                     id   = 1,
                                     sign = 0,
                                     link = 0)
                                     
        kdist_peer  = BLEGapSecKDist(enc  = 1,
                                     id   = 1,
                                     sign = 0,
                                     link = 0)
        
        sec_params  = BLEGapSecParams(bond          = True,
                                      mitm          = True,
                                      lesc          = True,
                                      oob           = False,
                                      keypress      = False,
                                      io_caps       = BLEGapIOCaps.keyboard_display,
                                      min_key_size  = 7,
                                      max_key_size  = 16,
                                      kdist_own     = kdist_own,
                                      kdist_peer    = kdist_peer)
        
        self.adapter.driver.ble_gap_authenticate(self.conn_handle, sec_params)
        
        self.adapter.evt_sync[self.conn_handle].wait(BLEEvtID.gap_evt_sec_params_request)
        self.adapter.driver.ble_gap_sec_params_reply(self.conn_handle,
                                                     BLEGapSecStatus.success,
                                                     sec_params = None)
                                        
        result = self.adapter.evt_sync[self.conn_handle].wait(BLEEvtID.gap_evt_auth_status)

        if result['auth_status'] ==  BLEGapSecStatus.success:
            self.keyset = adapter.db_conns[self.conn_handle]._keyset
            return True        

        return False
















def init(conn_ic_id):
    global config,          \
        BLEDriver,          \
        BLEAdvData,         \
        BLEEvtID,           \
        BLEAdapter,         \
        BLEEnableParams,    \
        BLEGapTimeoutSrc,   \
        BLEGapIOCaps,       \
        BLEUUID,            \
        BLEUUIDBase,        \
        BLEConfigCommon,    \
        BLEConfig,          \
        BLEConfigConnGatt,  \
        BLEGapScanParams,   \
        BLEGapSecKDist,     \
        BLEGapSecParams,    \
        BLEGapSecStatus,    \
        BLEGapSecKeyset

    
    from pc_ble_driver_py.ble_driver import (
                BLEDriver,          
                BLEAdvData,         
                BLEEvtID,           
                BLEEnableParams,    
                BLEGapTimeoutSrc,   
                BLEGapIOCaps,       
                BLEUUID,            
                BLEUUIDBase,        
                BLEConfigCommon,    
                BLEConfig,          
                BLEConfigConnGatt,  
                BLEGapScanParams,   
                BLEGapSecKDist,     
                BLEGapSecParams,    
                BLEGapSecStatus,
                BLEGapSecKeyset,
                util,              
                driver
    )

    from pc_ble_driver_py.ble_adapter import BLEAdapter

    global nrf_sd_ble_api_ver
    nrf_sd_ble_api_ver = config.sd_api_ver_get()

def main(selected_serial_port):
    print("Serial port used: {}".format(selected_serial_port))
    driver = BLEDriver(serial_port=selected_serial_port, auto_flash=False, baud_rate=1000000, log_severity_level="debug")

    adapter = BLEAdapter(driver)
    collector = HRCollector(adapter)
    collector.open()
    conn = collector.connect_and_discover()
    collector.authenticate()

    if conn is not None:
        time.sleep(10)

    collector.close()


def item_choose(item_list):
    for i, it in enumerate(item_list):
        print("\t{} : {}".format(i, it))
    print(" ")

    while True:
        try:
            choice = int(input("Enter your choice: "))
            if (choice >= 0) and (choice < len(item_list)):
                break
        except Exception:
            pass
        print("\tTry again...")
    return choice


if __name__ == "__main__":
    logging.basicConfig(level="DEBUG", format="%(asctime)s [%(thread)d/%(threadName)s] %(message)s")
    serial_port = None

    init("NRF51")
    if len(sys.argv) == 3:
        serial_port = sys.argv[2]
    else:
        descs = BLEDriver.enum_serial_ports()
        choices = ["{}: {}".format(d.port, d.serial_number) for d in descs]
        choice = item_choose(choices)
        serial_port = descs[choice].port
    main(serial_port)
    quit()

Many thanks

Related