Conversation
`Connection.authenticated` was set to true after every pairing, and again whenever LE encryption was enabled, so the GATT server gave access to attributes with READ/WRITE_REQUIRES_AUTHENTICATION to any peer that paired with Just Works. Derive the flag from the key that the link is encrypted with: - after pairing, from the `authenticated` flag of the keys that the pairing generated, which is false for Just Works - when encrypting with an LTK from the key store, from the `authenticated` flag of that key, applied once encryption is enabled. `sc` is set the same way, instead of always being set to true. Classic links are not changed.
zxzxwu
left a comment
There was a problem hiding this comment.
LGTM, thanks! Left two minor non-blocking nits inline.
Also a tiny heads-up: a few lines in the PR description appear to have been truncated when pasting (e.g. under "Notes for review" and "Compatibility").
| 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) |
There was a problem hiding this comment.
Nit: Consider moving the if connection.role != hci.Role.CENTRAL: check (just below) before looking up the key / setting connection.ltk_security, so a failed encrypt() call on a peripheral doesn't leave connection.ltk_security set.
There was a problem hiding this comment.
e2e75c1 gets the roll check run first before key store lookup; peripheral never records key's flags.
| # 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 |
There was a problem hiding this comment.
Nit: Should we also reset connection.ltk_security = None in on_connection_encryption_failure below so stale security flags aren't left on the connection if encryption with a stored LTK fails?
There was a problem hiding this comment.
e2e75c1 also clears ltk_security in on_connection_encryption_failure.
Address review comments: - Device.encrypt checks the role before looking up the key, so a call on a peripheral does not leave Connection.ltk_security set. - Connection.ltk_security is cleared when encryption fails.
Summary
Connection.authenticatedwas set to true after every pairing, and againwhenever LE encryption was enabled. The GATT server's
READ_REQUIRES_AUTHENTICATIONandWRITE_REQUIRES_AUTHENTICATIONchecks readthat flag, so a peer that paired with Just Works could read and write those
attributes. With the default
PairingDelegate, Just Works needs no userinteraction. The keys stored for the same pairing were already marked
authenticated: false.The flag is now derived from the key that the link is encrypted with.
Behavior
authenticatedbeforeauthenticatednowNotes for review
Device.on_pairingtakes the flag from the keys it is given,which
smp.Session.on_pairingalready marks according to the pairing method.smp.pyand theon_pairingsignature are not changed.key are recorded in a new
Connection.ltk_securitywhen the key is given tothe controller (
Device.get_long_term_keyas a peripheral,Device.encryptas a central), and applied when the Encryption Change event arrives.
Connection.ltk_securityis cleared when a pairing starts and whenencryption fails, so that the flags of a stored key that did not encrypt the
link are not applied to a later encryption.
Device.encryptchecks the role before it looks up the key, so a call on aperipheral does not record the flags of a key that is never used.
scis set the same way. Before, it was set to true on any LE encryptionchange, including with a legacy LTK.
with a legacy bond disconnects the link with the virtual controller, with or
without this change.
Compatibility
Authentication to a peer that paired with Just Works. In this repository that
applies to
apps/pair.pyandexamples/run_gatt_server_with_pairing_delegate.py.authenticatedandscare no longer set when encryptionstarts. They are set when the pairing completes, before the
pairingevent.Testing
Format, lint and the test suite pass, and mypy reports nothing in the changed
files. Three tests (8 cases) are added to
tests/self_test.py; the Just Workscases fail without the change.