forked from auracaster/bumble_mirror
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 624e860762 | |||
| 159cbf7774 | |||
| d188041694 | |||
| 99cba19d7c |
@@ -497,6 +497,8 @@ class Device(CompositeEventEmitter):
|
||||
self.smp_manager = smp.Manager(self, self.random_address)
|
||||
self.l2cap_channel_manager.register_fixed_channel(
|
||||
smp.SMP_CID, self.on_smp_pdu)
|
||||
self.l2cap_channel_manager.register_fixed_channel(
|
||||
smp.SMP_BR_CID, self.on_smp_pdu)
|
||||
|
||||
# Register the SDP server with the L2CAP Channel Manager
|
||||
self.sdp_server.register(self.l2cap_channel_manager)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+4
-3
@@ -44,12 +44,13 @@ HOST_HC_TOTAL_NUM_ACL_DATA_PACKETS = 1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Connection:
|
||||
def __init__(self, host, handle, role, peer_address):
|
||||
def __init__(self, host, handle, role, peer_address, transport):
|
||||
self.host = host
|
||||
self.handle = handle
|
||||
self.role = role
|
||||
self.peer_address = peer_address
|
||||
self.assembler = HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
||||
self.transport = transport
|
||||
|
||||
def on_hci_acl_data_packet(self, packet):
|
||||
self.assembler.feed_packet(packet)
|
||||
@@ -364,7 +365,7 @@ class Host(EventEmitter):
|
||||
|
||||
connection = self.connections.get(event.connection_handle)
|
||||
if connection is None:
|
||||
connection = Connection(self, event.connection_handle, event.role, event.peer_address)
|
||||
connection = Connection(self, event.connection_handle, event.role, event.peer_address, BT_LE_TRANSPORT)
|
||||
self.connections[event.connection_handle] = connection
|
||||
|
||||
# Notify the client
|
||||
@@ -399,7 +400,7 @@ class Host(EventEmitter):
|
||||
|
||||
connection = self.connections.get(event.connection_handle)
|
||||
if connection is None:
|
||||
connection = Connection(self, event.connection_handle, BT_CENTRAL_ROLE, event.bd_addr)
|
||||
connection = Connection(self, event.connection_handle, BT_CENTRAL_ROLE, event.bd_addr, BT_BR_EDR_TRANSPORT)
|
||||
self.connections[event.connection_handle] = connection
|
||||
|
||||
# Notify the client
|
||||
|
||||
+50
-15
@@ -44,6 +44,7 @@ logger = logging.getLogger(__name__)
|
||||
# Constants
|
||||
# -----------------------------------------------------------------------------
|
||||
SMP_CID = 0x06
|
||||
SMP_BR_CID = 0x07
|
||||
|
||||
SMP_PAIRING_REQUEST_COMMAND = 0x01
|
||||
SMP_PAIRING_RESPONSE_COMMAND = 0x02
|
||||
@@ -152,6 +153,8 @@ SMP_CT2_AUTHREQ = 0b00100000
|
||||
|
||||
# Crypto salt
|
||||
SMP_CTKD_H7_LEBR_SALT = bytes.fromhex('00000000000000000000000000000000746D7031')
|
||||
SMP_CTKD_H7_BRLE_SALT = bytes.fromhex('00000000000000000000000000000000746D7032')
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utils
|
||||
@@ -598,6 +601,7 @@ class Session:
|
||||
self.pairing_config = pairing_config
|
||||
self.wait_before_continuing = None
|
||||
self.completed = False
|
||||
self.ctkd_task = None
|
||||
|
||||
# Decide if we're the initiator or the responder
|
||||
self.is_initiator = (connection.role == BT_CENTRAL_ROLE)
|
||||
@@ -877,10 +881,21 @@ 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
|
||||
ilk = crypto.h7(
|
||||
salt=SMP_CTKD_H7_BRLE_SALT,
|
||||
w=link_key) if self.ct2 else crypto.h6(link_key, b'tmp2')
|
||||
self.ltk = crypto.h6(ilk, b'brle')
|
||||
|
||||
def distribute_keys(self):
|
||||
# Distribute the keys as required
|
||||
if self.is_initiator:
|
||||
if not self.sc:
|
||||
# CTKD: Derive LTK from LinkKey
|
||||
if self.connection.transport == BT_BR_EDR_TRANSPORT and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
|
||||
self.ctkd_task = asyncio.create_task(self.derive_ltk())
|
||||
elif not self.sc:
|
||||
# Distribute the LTK, EDIV and RAND
|
||||
if self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
|
||||
self.send_command(SMP_Encryption_Information_Command(long_term_key=self.ltk))
|
||||
@@ -900,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(
|
||||
@@ -909,8 +924,11 @@ class Session:
|
||||
self.link_key = crypto.h6(ilk, b'lebr')
|
||||
|
||||
else:
|
||||
# CTKD: Derive LTK from LinkKey
|
||||
if self.connection.transport == BT_BR_EDR_TRANSPORT and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
|
||||
self.ctkd_task = asyncio.create_task(self.derive_ltk())
|
||||
# Distribute the LTK, EDIV and RAND
|
||||
if not self.sc:
|
||||
elif not self.sc:
|
||||
if self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
|
||||
self.send_command(SMP_Encryption_Information_Command(long_term_key=self.ltk))
|
||||
self.send_command(SMP_Master_Identification_Command(ediv=self.ltk_ediv, rand=self.ltk_rand))
|
||||
@@ -929,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(
|
||||
@@ -940,7 +958,7 @@ class Session:
|
||||
def compute_peer_expected_distributions(self, key_distribution_flags):
|
||||
# Set our expectations for what to wait for in the key distribution phase
|
||||
self.peer_expected_distributions = []
|
||||
if not self.sc:
|
||||
if not self.sc and self.connection.transport == BT_LE_TRANSPORT:
|
||||
if (key_distribution_flags & SMP_ENC_KEY_DISTRIBUTION_FLAG != 0):
|
||||
self.peer_expected_distributions.append(SMP_Encryption_Information_Command)
|
||||
self.peer_expected_distributions.append(SMP_Master_Identification_Command)
|
||||
@@ -963,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
|
||||
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)
|
||||
@@ -989,17 +1002,28 @@ 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()
|
||||
|
||||
# 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
|
||||
self.on_connection_encryption_change()
|
||||
|
||||
def on_pairing(self):
|
||||
async def on_pairing(self):
|
||||
logger.debug('pairing complete')
|
||||
|
||||
if self.completed:
|
||||
@@ -1016,11 +1040,16 @@ class Session:
|
||||
else:
|
||||
peer_address = self.connection.peer_address
|
||||
|
||||
# Wait for link key fetch and key derivation
|
||||
if self.ctkd_task is not None:
|
||||
await self.ctkd_task
|
||||
self.ctkd_task = None
|
||||
|
||||
# Create an object to hold the keys
|
||||
keys = PairingKeys()
|
||||
keys.address_type = peer_address.address_type
|
||||
authenticated = self.pairing_method != self.JUST_WORKS
|
||||
if self.sc:
|
||||
if self.sc or self.connection.transport == BT_BR_EDR_TRANSPORT:
|
||||
keys.ltk = PairingKeys.Key(
|
||||
value = self.ltk,
|
||||
authenticated = authenticated
|
||||
@@ -1059,7 +1088,6 @@ class Session:
|
||||
value = self.link_key,
|
||||
authenticated = authenticated
|
||||
)
|
||||
|
||||
self.manager.on_pairing(self, peer_address, keys)
|
||||
|
||||
def on_pairing_failure(self, reason):
|
||||
@@ -1137,6 +1165,12 @@ class Session:
|
||||
# Respond
|
||||
self.send_pairing_response_command()
|
||||
|
||||
# Vol 3, Part C, 5.2.2.1.3
|
||||
# CTKD over BR/EDR should happen after the connection has been encrypted,
|
||||
# so when receiving pairing requests, responder should start distributing keys
|
||||
if self.connection.transport == BT_BR_EDR_TRANSPORT and self.connection.is_encrypted and self.is_responder and accepted:
|
||||
self.distribute_keys()
|
||||
|
||||
def on_smp_pairing_response_command(self, command):
|
||||
if self.is_responder:
|
||||
logger.warn(color('received pairing response as a responder', 'red'))
|
||||
@@ -1462,7 +1496,8 @@ class Manager(EventEmitter):
|
||||
|
||||
def send_command(self, connection, command):
|
||||
logger.debug(f'>>> Sending SMP Command on connection [0x{connection.handle:04X}] {connection.peer_address}: {command}')
|
||||
connection.send_l2cap_pdu(SMP_CID, command.to_bytes())
|
||||
cid = SMP_BR_CID if connection.transport == BT_BR_EDR_TRANSPORT else SMP_CID
|
||||
connection.send_l2cap_pdu(cid, command.to_bytes())
|
||||
|
||||
def on_smp_pdu(self, connection, pdu):
|
||||
# Look for a session with this connection, and create one if none exists
|
||||
|
||||
+1
-2
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user