Repository navigation
Do not mark an LE link as authenticated after Just Works pairing #1002
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1784,6 +1784,7 @@ class Connection(utils.CompositeEventEmitter): | |
| encryption_key_size: int | ||
| authenticated: bool | ||
| sc: bool | ||
| ltk_security: tuple[bool, bool] | None | ||
| gatt_client: gatt_client.Client | ||
| pairing_peer_io_capability: int | None | ||
| pairing_peer_authentication_requirements: int | None | ||
|
|
@@ -1915,6 +1916,9 @@ def __init__( | |
| self.encryption_key_size = 0 | ||
| self.authenticated = False | ||
| self.sc = False | ||
| # (authenticated, sc) of the stored LTK that was last given to the | ||
| # controller, applied once encryption with that key is enabled [LE only] | ||
| self.ltk_security = None | ||
| self.att_mtu = att.ATT_DEFAULT_MTU | ||
| self.data_length = self.LeDataLength(*DEVICE_DEFAULT_DATA_LENGTH) | ||
| self.gatt_client = gatt_client.Client(self) # Per-connection client | ||
|
|
@@ -4943,12 +4947,15 @@ async def get_long_term_key( | |
| if keys is not None: | ||
| logger.debug('found keys in the key store') | ||
| if keys.ltk: | ||
| connection.ltk_security = (keys.ltk.authenticated, True) | ||
| return keys.ltk.value | ||
|
|
||
| if connection.role == hci.Role.CENTRAL and keys.ltk_central: | ||
| connection.ltk_security = (keys.ltk_central.authenticated, False) | ||
| return keys.ltk_central.value | ||
|
|
||
| if connection.role == hci.Role.PERIPHERAL and keys.ltk_peripheral: | ||
| connection.ltk_security = (keys.ltk_peripheral.authenticated, False) | ||
| return keys.ltk_peripheral.value | ||
| return None | ||
|
|
||
|
|
@@ -5012,6 +5019,9 @@ def _(error_code: int): | |
| pending_encryption.set_exception(hci.HCI_Error(error_code)) | ||
|
|
||
| if connection.transport == PhysicalTransport.LE: | ||
| if connection.role != hci.Role.CENTRAL: | ||
| raise InvalidStateError('only centrals can start encryption') | ||
|
|
||
| # Look for a key in the key store | ||
| if self.keystore is None: | ||
| raise InvalidOperationError('no key store') | ||
|
|
@@ -5025,16 +5035,15 @@ def _(error_code: int): | |
| ltk = keys.ltk.value | ||
| rand = bytes(8) | ||
| ediv = 0 | ||
| connection.ltk_security = (keys.ltk.authenticated, True) | ||
| elif keys.ltk_central is not None: | ||
| ltk = keys.ltk_central.value | ||
| rand = keys.ltk_central.rand or b'' | ||
| ediv = keys.ltk_central.ediv or 0 | ||
| connection.ltk_security = (keys.ltk_central.authenticated, False) | ||
| else: | ||
| raise InvalidOperationError('no LTK found for peer') | ||
|
|
||
| if connection.role != hci.Role.CENTRAL: | ||
| raise InvalidStateError('only centrals can start encryption') | ||
|
|
||
| await self.send_async_command( | ||
| hci.HCI_LE_Enable_Encryption_Command( | ||
| connection_handle=connection.handle, | ||
|
|
@@ -6726,12 +6735,14 @@ def on_connection_encryption_change( | |
| connection.authenticated = True | ||
| connection.sc = True | ||
| if ( | ||
| not connection.authenticated | ||
| connection.ltk_security is not None | ||
| and connection.transport == PhysicalTransport.LE | ||
| and encryption == hci.HCI_Encryption_Change_Event.Enabled.E0_OR_AES_CCM | ||
| ): | ||
| connection.authenticated = True | ||
| connection.sc = True | ||
| # The link is only as secure as the stored key it is encrypted with. | ||
| # When pairing, this is set in `on_pairing` instead. | ||
| connection.authenticated, connection.sc = connection.ltk_security | ||
| connection.ltk_security = None | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Should we also reset
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_CHANGE) | ||
|
|
||
| @host_event_handler | ||
|
|
@@ -6742,6 +6753,8 @@ def on_connection_encryption_failure(self, connection, error): | |
| f'{connection.peer_address} as {connection.role_name}, ' | ||
| f'error={error}' | ||
| ) | ||
| # The stored key, if one was used, did not encrypt the link | ||
| connection.ltk_security = None | ||
| connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, error) | ||
|
|
||
| @host_event_handler | ||
|
|
@@ -7036,6 +7049,8 @@ def on_classic_pairing_failure(self, connection: Connection, status: int) -> Non | |
| self.emit(connection.EVENT_CLASSIC_PAIRING_FAILURE, connection, status) | ||
|
|
||
| def on_pairing_start(self, connection: Connection) -> None: | ||
| # The link will be encrypted with a new key, not with a stored one | ||
| connection.ltk_security = None | ||
| connection.emit(connection.EVENT_PAIRING_START) | ||
| self.emit(connection.EVENT_PAIRING_START, connection) | ||
|
|
||
|
|
@@ -7050,7 +7065,13 @@ def on_pairing( | |
| connection.peer_resolvable_address = connection.peer_address | ||
| connection.peer_address = identity_address | ||
| connection.sc = sc | ||
| connection.authenticated = True | ||
| # Only a pairing method with MITM protection (i.e. not Just Works) | ||
| # authenticates the link, which the keys that it generated keep track of. | ||
| connection.authenticated = any( | ||
| key.authenticated | ||
| for key in (keys.ltk, keys.ltk_central, keys.ltk_peripheral) | ||
| if key is not None | ||
| ) | ||
| connection.emit(connection.EVENT_PAIRING, keys) | ||
|
|
||
| def on_pairing_failure(self, connection: Connection, reason: int) -> None: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Consider moving the
if connection.role != hci.Role.CENTRAL:check (just below) before looking up the key / settingconnection.ltk_security, so a failedencrypt()call on a peripheral doesn't leaveconnection.ltk_securityset.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e2e75c1gets the roll check run first before key store lookup; peripheral never records key's flags.