Skip to content

fix(ble): notifications, write-with-response, LE secure connections check#202

Open
AlfioEmanueleFresta wants to merge 3 commits into
masterfrom
fix/ble-notifications-and-pairing
Open

fix(ble): notifications, write-with-response, LE secure connections check#202
AlfioEmanueleFresta wants to merge 3 commits into
masterfrom
fix/ble-notifications-and-pairing

Conversation

@AlfioEmanueleFresta
Copy link
Copy Markdown
Member

@AlfioEmanueleFresta AlfioEmanueleFresta commented May 10, 2026

Three FIDO-BLE protocol-level fixes, one per commit.

  • Response path uses notifications, not GATT Read. Per the FIDO BLE GATT service (CTAP 2.2 §11.4), fidoStatus advertises only the Notify property, so a spec-conforming peripheral rejects Read Value requests with Not Permitted. The library was reading instead of consuming notifications, which made FIDO2-over-BLE non-functional against spec-conformant peripherals on bluez. Connection::new now subscribes to fidoStatus, filters the peripheral notification stream to that UUID, and frame_recv awaits the next notification with the caller-supplied operation timeout. On timeout it emits a best-effort BleCommand::Cancel on fidoControlPoint and returns TransportError::Timeout.
  • Writes use Write Request, not Write Command. fidoControlPoint and fidoServiceRevisionBitfield are Write characteristics; using Write-Without-Response masked ATT-level errors (e.g. encryption-required) and was rejected outright by some authenticators. A new write_type_for() helper inspects the characteristic's declared GATT properties and only picks WithoutResponse when that is the sole property advertised. The U2F predecessor lists u2fStatus as notify-only and u2fControlPoint as Write in FIDO U2F BT v1.2 §6.1.
  • Verify LE Secure Connections bonding before exchanging FIDO data. FIDO BLE requires bonding over LE Secure Connections (CTAP 2.2 §11.4 Pairing / Link Security). The library now queries bluez's org.bluez.Device1.{Paired,Bonded} properties on connect via DBus, dispatched on spawn_blocking so it doesn't stall the runtime; non-bonded devices are refused with ConnectionFailed. On non-bluez backends or when DBus is unreachable, the check falls through and defers to the OS pairing UI (mandatory for authenticated GATT characteristics on macOS / Windows). The library itself cannot trigger pairing; users are expected to pair via the OS (e.g. bluetoothctl pair <ADDR>).

Test plan

Automated coverage is limited because BLE tests require hardware; this PR adds:

  • Unit tests for write_type_for covering each GATT property combination.
  • cargo build and cargo test --lib pass (139 tests).
  • cargo fmt --check and cargo clippy --lib --all-features -- -D warnings clean.

Manual test plan (requires a paired BLE FIDO authenticator):

  • Bonded, FIDO2: pair a BLE FIDO authenticator (e.g. Yubico Security Key NFC / 5C NFC, Feitian BioPass BLE), run examples/webauthn_or_u2f, confirm MakeCredential + GetAssertion succeed.
  • Unbonded: remove the bond (bluetoothctl remove <ADDR> then re-discover without pairing); confirm the library refuses to proceed with a clear error.
  • Timeout path: connect to an idle authenticator and confirm frame_recv returns after the per-op timeout and a Cancel packet is emitted (visible via btmon).
  • Non-bluez fallback: smoke-build on macOS / Windows targets; the bonding check should return Unknown and let GATT proceed.

@AlfioEmanueleFresta AlfioEmanueleFresta force-pushed the fix/ble-notifications-and-pairing branch from 618eae0 to a1ef9d4 Compare May 10, 2026 20:33
…eristics

Per CTAP 2.2 §11.4, both fidoControlPoint and fidoServiceRevisionBitfield
expose the standard Write property (GATT Write Request, with response).
The previous code always issued WriteType::WithoutResponse, which is
spec-incorrect and may silently drop bytes on conforming authenticators.

Introduces a write_type_for() helper that inspects the characteristic's
declared GATT properties and picks WithResponse when WRITE is set, falling
back to WithoutResponse only when the authenticator explicitly advertises
just that property. Unit tests cover the property-detection logic.
Per CTAP 2.2 §11.4 the fidoStatus characteristic is Notify-only. The
previous receive path called peripheral.read() against it, which bluez
rejects with NotPermitted and makes FIDO2-over-BLE non-functional on
Linux. Other backends may tolerate the Read by returning stale cached
data, which is not real notification-driven framing either way.

Connection::new now subscribes to fidoStatus, obtains the peripheral's
notification stream, filters it to the fidoStatus UUID, and stores it
on the connection. frame_recv awaits the next notification with the
caller-supplied operation timeout; on expiry it sends a BleCommand::Cancel
on fidoControlPoint (best-effort, WithoutResponse) and returns Timeout.

The channel's apdu_recv and cbor_recv now forward their timeout to
frame_recv and map Error::Timeout to TransportError::Timeout.
Per CTAP 2.2 §11.4, BLE FIDO authenticator traffic MUST run on a
bonded LE Secure Connections link. The previous connect path issued
FIDO operations without verifying bonding state, so a session that
fell back to an unauthenticated link would proceed in violation of
the spec.

Adds a pairing module that, on Linux, queries
org.bluez.Device1.Paired and org.bluez.Device1.Bonded over DBus and
refuses to proceed if either is false. The DBus call is dispatched on
spawn_blocking so it doesn't stall the tokio runtime. On non-bluez
backends or when DBus is unreachable the check falls back gracefully:
macOS and Windows enforce bonding at the OS level for authenticated
GATT characteristics, so deferring there is acceptable. The library
itself cannot trigger pairing; the user is expected to pair the device
beforehand (e.g. via bluetoothctl).

manager::connect calls enforce_bonded after establishing the link and
before discovering services.
@AlfioEmanueleFresta AlfioEmanueleFresta force-pushed the fix/ble-notifications-and-pairing branch from a1ef9d4 to d8142f1 Compare May 12, 2026 18:38
@AlfioEmanueleFresta AlfioEmanueleFresta marked this pull request as ready for review May 12, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant