diff --git a/docs/library/bluetooth.rst b/docs/library/bluetooth.rst index b09c370abd46d..f5c5fa979d7a4 100644 --- a/docs/library/bluetooth.rst +++ b/docs/library/bluetooth.rst @@ -733,6 +733,11 @@ Pairing and bonding On successful pairing, the ``_IRQ_ENCRYPTION_UPDATE`` event will be raised. +.. method:: BLE.gap_unpair(key, /) + + Removes pairing details from the bond database, where ``key`` is the entry key + as provided in _IRQ_GET_SECRET/_IRQ_SET_SECRET events. + .. method:: BLE.gap_passkey(conn_handle, action, passkey, /) Respond to a ``_IRQ_PASSKEY_ACTION`` event for the specified *conn_handle* diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 7694a1874f40b..26fc546f1ac28 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -1265,6 +1265,27 @@ int mp_bluetooth_gap_pair(uint16_t conn_handle) { return 0; } +int mp_bluetooth_gap_unpair(uint8_t *key, size_t key_len) { + DEBUG_printf("mp_bluetooth_gap_unpair\n"); + if (BD_ADDR_LEN != key_len) { + mp_raise_ValueError(MP_ERROR_TEXT("Incorrect key length")); + } + + int addr_type; + bd_addr_t addr; + sm_key_t irk; + for (int i = 0; i < MAX_NR_LE_DEVICE_DB_ENTRIES; i++) { + le_device_db_info(i, &addr_type, addr, irk); + if (addr_type != BD_ADDR_TYPE_UNKNOWN) { + if (0 == memcmp(key, addr, BD_ADDR_LEN)) { + le_device_db_remove(i); + return 0; + } + } + } + return MP_ENOENT; +} + int mp_bluetooth_gap_passkey(uint16_t conn_handle, uint8_t action, mp_int_t passkey) { DEBUG_printf("mp_bluetooth_gap_passkey: conn_handle=%d action=%d passkey=%d\n", conn_handle, action, (int)passkey); return MP_EOPNOTSUPP; diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index ffa407809aa71..d5daab5f4ae41 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -717,6 +717,21 @@ static mp_obj_t bluetooth_ble_gap_pair(mp_obj_t self_in, mp_obj_t conn_handle_in } static MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_pair_obj, bluetooth_ble_gap_pair); +static mp_obj_t bluetooth_ble_gap_unpair(mp_obj_t self_in, mp_obj_t key_buff) { + (void)self_in; + + uint8_t *key = NULL; + size_t key_len = 0; + + mp_buffer_info_t key_bufinfo = {0}; + mp_get_buffer_raise(key_buff, &key_bufinfo, MP_BUFFER_READ); + key = key_bufinfo.buf; + key_len = key_bufinfo.len; + + return bluetooth_handle_errno(mp_bluetooth_gap_unpair(key, key_len)); +} +static MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_unpair_obj, bluetooth_ble_gap_unpair); + static mp_obj_t bluetooth_ble_gap_passkey(size_t n_args, const mp_obj_t *args) { uint16_t conn_handle = mp_obj_get_int(args[1]); uint8_t action = mp_obj_get_int(args[2]); @@ -945,6 +960,7 @@ static const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gap_disconnect), MP_ROM_PTR(&bluetooth_ble_gap_disconnect_obj) }, #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING { MP_ROM_QSTR(MP_QSTR_gap_pair), MP_ROM_PTR(&bluetooth_ble_gap_pair_obj) }, + { MP_ROM_QSTR(MP_QSTR_gap_unpair), MP_ROM_PTR(&bluetooth_ble_gap_unpair_obj) }, { MP_ROM_QSTR(MP_QSTR_gap_passkey), MP_ROM_PTR(&bluetooth_ble_gap_passkey_obj) }, #endif // GATT Server diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 24f063fa5d617..6218f1f366576 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -358,6 +358,9 @@ int mp_bluetooth_set_preferred_mtu(uint16_t mtu); // Initiate pairing on the specified connection. int mp_bluetooth_gap_pair(uint16_t conn_handle); +// Remove a specific pairing key from the radio. +int mp_bluetooth_gap_unpair(uint8_t *key, size_t key_len); + // Respond to a pairing request. int mp_bluetooth_gap_passkey(uint16_t conn_handle, uint8_t action, mp_int_t passkey); #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 5e7030e36fab4..b31a6ee15e62c 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -1111,6 +1111,15 @@ int mp_bluetooth_gap_pair(uint16_t conn_handle) { return ble_hs_err_to_errno(ble_gap_security_initiate(conn_handle)); } +int mp_bluetooth_gap_unpair(uint8_t *key, size_t key_len) { + if (sizeof(ble_addr_t) != key_len) { + mp_raise_ValueError(MP_ERROR_TEXT("Incorrect key length")); + } + + DEBUG_printf("mp_bluetooth_gap_unpair: specific\n"); + return ble_hs_err_to_errno(ble_gap_unpair((ble_addr_t *)key)); +} + int mp_bluetooth_gap_passkey(uint16_t conn_handle, uint8_t action, mp_int_t passkey) { struct ble_sm_io io = {0}; diff --git a/tests/multi_bluetooth/ble_gap_unpair.py b/tests/multi_bluetooth/ble_gap_unpair.py new file mode 100644 index 0000000000000..c9e57b5a683c2 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_unpair.py @@ -0,0 +1,194 @@ +# Test BLE GAP unpair functionality +# Tests the new gap_unpair method added to MicroPython +# gap_unpair expects a key from _IRQ_GET_SECRET/_IRQ_SET_SECRET events + +from micropython import const +import time, machine, bluetooth + +if not hasattr(bluetooth.BLE, "gap_unpair"): + print("SKIP") + raise SystemExit + +TIMEOUT_MS = 4000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_ENCRYPTION_UPDATE = const(28) +_IRQ_GET_SECRET = const(29) +_IRQ_SET_SECRET = const(30) + +_FLAG_READ = const(0x0002) +_FLAG_READ_ENCRYPTED = const(0x0200) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = (CHAR_UUID, _FLAG_READ | _FLAG_READ_ENCRYPTED) +SERVICE = (SERVICE_UUID, (CHAR,)) + +waiting_events = {} +bond_keys = [] # Store bond keys for unpair testing + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_READ_REQUEST: + print("_IRQ_GATTS_READ_REQUEST") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_READ_RESULT: + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_ENCRYPTION_UPDATE: + print("_IRQ_ENCRYPTION_UPDATE", data[1], data[2], data[3]) + elif event == _IRQ_GET_SECRET: + print("_IRQ_GET_SECRET", "key:", data[1]) + bond_keys.append(data[1]) # Store the key for unpair testing + elif event == _IRQ_SET_SECRET: + print("_IRQ_SET_SECRET", "key:", data[1]) + bond_keys.append(data[1]) # Store the key for unpair testing + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services((SERVICE,)) + ble.gatts_write(char_handle, "encrypted") + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect. + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + + # Wait for pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Wait for GATTS read request. + wait_for_event(_IRQ_GATTS_READ_REQUEST, TIMEOUT_MS) + + multitest.next() + + # Wait for central to disconnect after initial pairing. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + + # Test gap_unpair functionality + print("gap_unpair_test") + print("bond_keys_captured:", len(bond_keys)) + + # Test gap_unpair with captured bond keys + if bond_keys: + for i, key in enumerate(bond_keys): + try: + result = ble.gap_unpair(key) + print(f"gap_unpair_key_{i}_result:", result) + except Exception as e: + print(f"gap_unpair_key_{i}_error:", type(e).__name__, str(e)) + else: + print("gap_unpair_no_keys_captured") + + # Test unpair with non-existent key + fake_key = b'\x01\x12\x34\x56\x78\x9a\xbc\xde\xf0\x11\x22\x33\x44\x55\x66\x77' + try: + result = ble.gap_unpair(fake_key) + print("gap_unpair_fake_key_result:", result) + except Exception as e: + print("gap_unpair_fake_key_error:", type(e).__name__, str(e)) + + # Test unpair with wrong key format (should fail) + try: + result = ble.gap_unpair(b'\x01\x02\x03\x04\x05\x06') # Too short + print("gap_unpair_wrong_key_result:", result) + except Exception as e: + print("gap_unpair_wrong_key_error:", type(e).__name__) + + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + # Connect to peripheral. + print("gap_connect") + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + # Discover characteristics (before pairing, doesn't need to be encrypted). + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + # Pair with the peripheral. + print("gap_pair") + ble.gap_pair(conn_handle) + + # Wait for the pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Read the peripheral's characteristic, should be encrypted. + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + + multitest.next() + + # Disconnect from the peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) + + # Test gap_unpair on central side too + print("central_gap_unpair_test") + print("central_bond_keys_captured:", len(bond_keys)) + + # Test gap_unpair with captured bond keys on central side + if bond_keys: + for i, key in enumerate(bond_keys): + try: + result = ble.gap_unpair(key) + print(f"central_gap_unpair_key_{i}_result:", result) + except Exception as e: + print(f"central_gap_unpair_key_{i}_error:", type(e).__name__, str(e)) + else: + print("central_gap_unpair_no_keys") + + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.config(mitm=True, le_secure=True, bond=True) # Enable bonding for unpair test +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_gap_unpair.py.exp b/tests/multi_bluetooth/ble_gap_unpair.py.exp new file mode 100644 index 0000000000000..72f3b5fc4eb75 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_unpair.py.exp @@ -0,0 +1,41 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_GET_SECRET key: 0 +_IRQ_GET_SECRET key: 0 +_IRQ_GET_SECRET key: 0 +_IRQ_SET_SECRET key: +_IRQ_GET_SECRET key: 0 +_IRQ_SET_SECRET key: +_IRQ_GET_SECRET key: 0 +_IRQ_ENCRYPTION_UPDATE 1 0 1 +_IRQ_GATTS_READ_REQUEST +_IRQ_CENTRAL_DISCONNECT +gap_unpair_test +bond_keys_captured: 3 +gap_unpair_key_0_result: None +gap_unpair_key_1_result: None +gap_unpair_key_2_result: None +gap_unpair_fake_key_error: ValueError +gap_unpair_wrong_key_error: ValueError +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gap_pair +_IRQ_GET_SECRET key: 0 +_IRQ_ENCRYPTION_UPDATE 1 0 1 +_IRQ_SET_SECRET key: +_IRQ_GET_SECRET key: 0 +_IRQ_SET_SECRET key: +_IRQ_GET_SECRET key: 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'encrypted' +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +central_gap_unpair_test +central_bond_keys_captured: 3 +central_gap_unpair_key_0_result: None +central_gap_unpair_key_1_result: None +central_gap_unpair_key_2_result: None \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy