From c3daf4a7e10cd0dc431a608aac9bc6ce57a7758a Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Fri, 3 Apr 2026 23:02:15 +0200 Subject: [PATCH 1/7] implement debug mode for smp manager using defined private / public key pair --- bumble/smp.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/bumble/smp.py b/bumble/smp.py index 9d0bb7c..e722b1f 100644 --- a/bumble/smp.py +++ b/bumble/smp.py @@ -178,6 +178,16 @@ class AuthReq(hci.SpecableFlag): SMP_CTKD_H7_LEBR_SALT = bytes.fromhex('000000000000000000000000746D7031') SMP_CTKD_H7_BRLE_SALT = bytes.fromhex('000000000000000000000000746D7032') +# Diffie-Hellman private / public key pair in Debug Mode (Core - Vol. 3, Part H) +SMP_DEBUG_KEY_PRIVATE = bytes.fromhex( + '3f49f6d4 a3c55f38 74c9b3e3 d2103f50 4aff607b eb40b799 5899b8a6 cd3c1abd' + ) +SMP_DEBUG_KEY_PUBLIC_X = bytes.fromhex( + '20b003d2 f297be2c 5e2c83a7 e9f9a5b9 eff49111 acf4fddb cc030148 0e359de6' + ) +SMP_DEBUG_KEY_PUBLIC_Y= bytes.fromhex( + 'dc809c49 652aeb6d 63329abf 5a52155c 766345c2 8fed3024 741c8ed0 1589d28b' + ) # fmt: on # pylint: enable=line-too-long # pylint: disable=invalid-name @@ -187,6 +197,8 @@ SMP_CTKD_H7_BRLE_SALT = bytes.fromhex('000000000000000000000000746D7032') # Utils # ----------------------------------------------------------------------------- +# Test DH with test vectors from the spec + # ----------------------------------------------------------------------------- # Classes @@ -1919,6 +1931,7 @@ class Manager(utils.EventEmitter): self._ecc_key = None self.pairing_config_factory = pairing_config_factory self.session_proxy = Session + self.debug_mode = False def send_command(self, connection: Connection, command: SMP_Command) -> None: logger.debug( @@ -1965,6 +1978,15 @@ class Manager(utils.EventEmitter): @property def ecc_key(self) -> crypto.EccKey: + if self.debug_mode: + # Core - Vol 3, Part H: + # When the Security Manager is placed in a Debug mode it shall use the + # following Diffie-Hellman private / public key pair: + debug_key = crypto.EccKey.from_private_key_bytes(SMP_DEBUG_KEY_PRIVATE) + assert debug_key.x == SMP_DEBUG_KEY_PUBLIC_X + assert debug_key.y == SMP_DEBUG_KEY_PUBLIC_Y + return debug_key + if self._ecc_key is None: self._ecc_key = crypto.EccKey.generate() assert self._ecc_key From ee09e6f10da5642844ad939c4c68731b66e4bf4d Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Fri, 3 Apr 2026 23:03:51 +0200 Subject: [PATCH 2/7] add smp_debug_mode config flag to enable debug keys during device init --- bumble/device.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/bumble/device.py b/bumble/device.py index 65eba87..7d0f2e7 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -558,9 +558,7 @@ class AdvertisingParameters: ) primary_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL primary_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL - primary_advertising_channel_map: ( - hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap - ) = ( + primary_advertising_channel_map: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap = ( AdvertisingChannelMap.CHANNEL_37 | AdvertisingChannelMap.CHANNEL_38 | AdvertisingChannelMap.CHANNEL_39 @@ -583,9 +581,7 @@ class AdvertisingParameters: class PeriodicAdvertisingParameters: periodic_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL periodic_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL - periodic_advertising_properties: ( - hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties - ) = field( + periodic_advertising_properties: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties = field( default_factory=lambda: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties( 0 ) @@ -897,9 +893,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter): options = hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options(0) if self.filter_duplicates: - options |= ( - hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED - ) + options |= hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED await self.device.send_async_command( hci.HCI_LE_Periodic_Advertising_Create_Sync_Command( @@ -1579,12 +1573,8 @@ class CigParameters: worst_case_sca: WorstCaseSca = WorstCaseSca.SCA_251_TO_500_PPM packing: Packing = Packing.SEQUENTIAL framing: Framing = Framing.UNFRAMED - max_transport_latency_c_to_p: int = ( - DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds - ) - max_transport_latency_p_to_c: int = ( - DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds - ) + max_transport_latency_c_to_p: int = DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds + max_transport_latency_p_to_c: int = DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds # ----------------------------------------------------------------------------- @@ -2159,6 +2149,7 @@ class DeviceConfiguration: ) eatt_enabled: bool = False gatt_services: list[dict[str, Any]] = field(init=False) + smp_debug_mode: bool = False def __post_init__(self) -> None: self.gatt_services = [] @@ -2450,9 +2441,7 @@ class Device(utils.CompositeEventEmitter): self.le_connecting = False self.disconnecting = False self.connections = {} # Connections, by connection handle - self.pending_connections = ( - {} - ) # Pending connections, by BD address (BR/EDR only) + self.pending_connections = {} # Pending connections, by BD address (BR/EDR only) self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only) self.cis_links = {} # CisLinks, by connection handle (LE only) self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle @@ -2571,6 +2560,7 @@ class Device(utils.CompositeEventEmitter): ), ), ) + self.smp_manager.debug_mode = self.config.smp_debug_mode self.l2cap_channel_manager.register_fixed_channel(smp.SMP_CID, self.on_smp_pdu) From e85f041e9dba7e6379ee3604a3bb006975e49c88 Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Fri, 3 Apr 2026 23:04:48 +0200 Subject: [PATCH 3/7] add test for smp debug mode --- tests/smp_test.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/smp_test.py b/tests/smp_test.py index f48c309..942fac0 100644 --- a/tests/smp_test.py +++ b/tests/smp_test.py @@ -24,7 +24,7 @@ import pytest from bumble import crypto, pairing, smp from bumble.core import AdvertisingData from bumble.crypto import EccKey, aes_cmac, ah, c1, f4, f5, f6, g2, h6, h7, s1 -from bumble.device import Device +from bumble.device import Device, DeviceConfiguration from bumble.hci import Address from bumble.pairing import LeRole, OobData, OobSharedData @@ -312,3 +312,18 @@ async def test_send_identity_address_command( actual_command = mock_method.call_args.args[0] assert actual_command.addr_type == expected_identity_address.address_type assert actual_command.bd_addr == expected_identity_address + + +@pytest.mark.asyncio +async def test_smp_debug_mode(): + config = DeviceConfiguration(smp_debug_mode=True) + device = Device(config=config) + + # assert device.smp_manager.ecc_key.private_key == smp.SMP_DEBUG_KEY_PRIVATE + assert device.smp_manager.ecc_key.x == smp.SMP_DEBUG_KEY_PUBLIC_X + assert device.smp_manager.ecc_key.y == smp.SMP_DEBUG_KEY_PUBLIC_Y + + device.smp_manager.debug_mode = False + + assert not device.smp_manager.ecc_key.x == smp.SMP_DEBUG_KEY_PUBLIC_X + assert not device.smp_manager.ecc_key.y == smp.SMP_DEBUG_KEY_PUBLIC_Y From 85f6b1098345b96ce780d78495b3f89e05373123 Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Fri, 3 Apr 2026 23:06:24 +0200 Subject: [PATCH 4/7] run formatter --- bumble/device.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/bumble/device.py b/bumble/device.py index 7d0f2e7..21ca31d 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -558,7 +558,9 @@ class AdvertisingParameters: ) primary_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL primary_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL - primary_advertising_channel_map: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap = ( + primary_advertising_channel_map: ( + hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap + ) = ( AdvertisingChannelMap.CHANNEL_37 | AdvertisingChannelMap.CHANNEL_38 | AdvertisingChannelMap.CHANNEL_39 @@ -581,7 +583,9 @@ class AdvertisingParameters: class PeriodicAdvertisingParameters: periodic_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL periodic_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL - periodic_advertising_properties: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties = field( + periodic_advertising_properties: ( + hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties + ) = field( default_factory=lambda: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties( 0 ) @@ -893,7 +897,9 @@ class PeriodicAdvertisingSync(utils.EventEmitter): options = hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options(0) if self.filter_duplicates: - options |= hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED + options |= ( + hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED + ) await self.device.send_async_command( hci.HCI_LE_Periodic_Advertising_Create_Sync_Command( @@ -1573,8 +1579,12 @@ class CigParameters: worst_case_sca: WorstCaseSca = WorstCaseSca.SCA_251_TO_500_PPM packing: Packing = Packing.SEQUENTIAL framing: Framing = Framing.UNFRAMED - max_transport_latency_c_to_p: int = DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds - max_transport_latency_p_to_c: int = DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds + max_transport_latency_c_to_p: int = ( + DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds + ) + max_transport_latency_p_to_c: int = ( + DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds + ) # ----------------------------------------------------------------------------- @@ -2441,7 +2451,9 @@ class Device(utils.CompositeEventEmitter): self.le_connecting = False self.disconnecting = False self.connections = {} # Connections, by connection handle - self.pending_connections = {} # Pending connections, by BD address (BR/EDR only) + self.pending_connections = ( + {} + ) # Pending connections, by BD address (BR/EDR only) self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only) self.cis_links = {} # CisLinks, by connection handle (LE only) self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle From 25a0056ecc1dc9c5012f5fbe12c422ce1eb9d72d Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Fri, 3 Apr 2026 23:08:16 +0200 Subject: [PATCH 5/7] remove uncommented line --- tests/smp_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/smp_test.py b/tests/smp_test.py index 942fac0..f3b5e96 100644 --- a/tests/smp_test.py +++ b/tests/smp_test.py @@ -319,7 +319,6 @@ async def test_smp_debug_mode(): config = DeviceConfiguration(smp_debug_mode=True) device = Device(config=config) - # assert device.smp_manager.ecc_key.private_key == smp.SMP_DEBUG_KEY_PRIVATE assert device.smp_manager.ecc_key.x == smp.SMP_DEBUG_KEY_PUBLIC_X assert device.smp_manager.ecc_key.y == smp.SMP_DEBUG_KEY_PUBLIC_Y From 3f65380c20f8789738ef8bd5ebc78a1a246f304b Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Fri, 3 Apr 2026 23:19:43 +0200 Subject: [PATCH 6/7] remove comment --- bumble/smp.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bumble/smp.py b/bumble/smp.py index e722b1f..bee616f 100644 --- a/bumble/smp.py +++ b/bumble/smp.py @@ -197,8 +197,6 @@ SMP_DEBUG_KEY_PUBLIC_Y= bytes.fromhex( # Utils # ----------------------------------------------------------------------------- -# Test DH with test vectors from the spec - # ----------------------------------------------------------------------------- # Classes From dc17f4f1ca6532edfd3b5afa175fcb2afb9f3cd5 Mon Sep 17 00:00:00 2001 From: Markus Jellitsch Date: Wed, 8 Apr 2026 20:58:47 +0200 Subject: [PATCH 7/7] remove asserts --- bumble/smp.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bumble/smp.py b/bumble/smp.py index bee616f..0f40094 100644 --- a/bumble/smp.py +++ b/bumble/smp.py @@ -1981,8 +1981,6 @@ class Manager(utils.EventEmitter): # When the Security Manager is placed in a Debug mode it shall use the # following Diffie-Hellman private / public key pair: debug_key = crypto.EccKey.from_private_key_bytes(SMP_DEBUG_KEY_PRIVATE) - assert debug_key.x == SMP_DEBUG_KEY_PUBLIC_X - assert debug_key.y == SMP_DEBUG_KEY_PUBLIC_Y return debug_key if self._ecc_key is None: