From 159cbf77745cd44a5b02711a11179e6789708d2f Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Tue, 30 Aug 2022 18:28:24 -0700 Subject: [PATCH 1/2] support pairing with no key distribution --- bumble/helpers.py | 5 +++++ bumble/smp.py | 25 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/bumble/helpers.py b/bumble/helpers.py index 09b86efe..e9ebf972 100644 --- a/bumble/helpers.py +++ b/bumble/helpers.py @@ -18,6 +18,8 @@ import logging from colors import color +from bumble.smp import SMP_CID, SMP_Command + from .core import name_or_number from .gatt import ATT_PDU, ATT_CID from .l2cap import ( @@ -73,6 +75,9 @@ class PacketTracer: if l2cap_pdu.cid == ATT_CID: att_pdu = ATT_PDU.from_bytes(l2cap_pdu.payload) self.analyzer.emit(att_pdu) + elif l2cap_pdu.cid == SMP_CID: + smp_command = SMP_Command.from_bytes(l2cap_pdu.payload) + self.analyzer.emit(smp_command) elif l2cap_pdu.cid == L2CAP_SIGNALING_CID or l2cap_pdu.cid == L2CAP_LE_SIGNALING_CID: control_frame = L2CAP_Control_Frame.from_bytes(l2cap_pdu.payload) self.analyzer.emit(control_frame) diff --git a/bumble/smp.py b/bumble/smp.py index 17ca6edf..9765e618 100644 --- a/bumble/smp.py +++ b/bumble/smp.py @@ -155,6 +155,7 @@ SMP_CT2_AUTHREQ = 0b00100000 SMP_CTKD_H7_LEBR_SALT = bytes.fromhex('00000000000000000000000000000000746D7031') SMP_CTKD_H7_BRLE_SALT = bytes.fromhex('00000000000000000000000000000000746D7032') + # ----------------------------------------------------------------------------- # Utils # ----------------------------------------------------------------------------- @@ -879,7 +880,7 @@ class Session: ) ) ) - + async def derive_ltk(self): link_key = await self.manager.device.get_link_key(self.connection.peer_address) assert link_key is not None @@ -914,7 +915,7 @@ class Session: csrk = bytes(16) # FIXME: testing if self.initiator_key_distribution & SMP_SIGN_KEY_DISTRIBUTION_FLAG: self.send_command(SMP_Signing_Information_Command(signature_key=csrk)) - + # CTKD, calculate BR/EDR link key if self.initiator_key_distribution & SMP_LINK_KEY_DISTRIBUTION_FLAG: ilk = crypto.h7( @@ -946,7 +947,7 @@ class Session: csrk = bytes(16) # FIXME: testing if self.responder_key_distribution & SMP_SIGN_KEY_DISTRIBUTION_FLAG: self.send_command(SMP_Signing_Information_Command(signature_key=csrk)) - + # CTKD, calculate BR/EDR link key if self.responder_key_distribution & SMP_LINK_KEY_DISTRIBUTION_FLAG: ilk = crypto.h7( @@ -980,12 +981,7 @@ class Session: self.peer_expected_distributions.remove(command_class) logger.debug(f'remaining distributions: {[c.__name__ for c in self.peer_expected_distributions]}') if not self.peer_expected_distributions: - # The initiator can now send its keys - if self.is_initiator: - self.distribute_keys() - - # Nothing left to expect, we're done - asyncio.create_task(self.on_pairing()) + self.on_peer_key_distribution_complete() else: logger.warn(color(f'!!! unexpected key distribution command: {command_class.__name__}', 'red')) self.send_pairing_failed(SMP_UNSPECIFIED_REASON_ERROR) @@ -1006,11 +1002,22 @@ class Session: self.connection.remove_listener('connection_encryption_key_refresh', self.on_connection_encryption_key_refresh) self.manager.on_session_end(self) + def on_peer_key_distribution_complete(self): + # The initiator can now send its keys + if self.is_initiator: + self.distribute_keys() + + asyncio.create_task(self.on_pairing()) + def on_connection_encryption_change(self): if self.connection.is_encrypted: if self.is_responder: # The responder distributes its keys first, the initiator later self.distribute_keys() + else: + # If we're not expecting key distributions from the peer, we're done + if not self.peer_expected_distributions: + self.on_peer_key_distribution_complete() def on_connection_encryption_key_refresh(self): # Do as if the connection had just been encrypted From 624e8607629e3f46d7d1f8399aa3c1afe93318fc Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Tue, 30 Aug 2022 18:50:48 -0700 Subject: [PATCH 2/2] support empty distributions in both directions --- bumble/smp.py | 8 ++++---- tests/self_test.py | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bumble/smp.py b/bumble/smp.py index 9765e618..c544a821 100644 --- a/bumble/smp.py +++ b/bumble/smp.py @@ -1014,10 +1014,10 @@ class Session: if self.is_responder: # The responder distributes its keys first, the initiator later self.distribute_keys() - else: - # If we're not expecting key distributions from the peer, we're done - if not self.peer_expected_distributions: - self.on_peer_key_distribution_complete() + + # If we're not expecting key distributions from the peer, we're done + if not self.peer_expected_distributions: + self.on_peer_key_distribution_complete() def on_connection_encryption_key_refresh(self): # Do as if the connection had just been encrypted diff --git a/tests/self_test.py b/tests/self_test.py index cf1befff..7316b2fe 100644 --- a/tests/self_test.py +++ b/tests/self_test.py @@ -246,8 +246,7 @@ IO_CAP = [ SC = [False, True] MITM = [False, True] # Key distribution is a 4-bit bitmask -# IdKey is necessary for current SMP structure -KEY_DIST = [i for i in range(16) if (i & SMP_ID_KEY_DISTRIBUTION_FLAG)] +KEY_DIST = range(16) @pytest.mark.asyncio @pytest.mark.parametrize('io_cap, sc, mitm, key_dist',