feat(cable): hybrid transport over a direct BLE L2CAP channel#216
Draft
AlfioEmanueleFresta wants to merge 17 commits into
Draft
feat(cable): hybrid transport over a direct BLE L2CAP channel#216AlfioEmanueleFresta wants to merge 17 commits into
AlfioEmanueleFresta wants to merge 17 commits into
Conversation
Member
|
Got an error: |
Adds a message-oriented duplex channel trait so the Noise handshake and encrypted CTAP framing can run over any transport, plus a WebSocket implementation. Unused until the protocol layer is moved onto it.
…ction Lifts the Noise handshake, CableTunnelMessage framing, padding, the connection loop and post-handshake/update parsing out of tunnel.rs into a new protocol.rs, generic over a CableDataChannel instead of a WebSocketStream. tunnel.rs keeps the WebSocket connect and tunnel-domain decoding. connection_stages.rs carries a Box<dyn CableDataChannel>. caBLE over WebSocket behaves identically; the abstraction lets a future data channel (BLE L2CAP) plug in.
Accept BLE service data of length >= 20: trial-decrypt only the first 20 bytes, then parse any remainder as the CTAP 2.3 PXP advertisement suffix, a CBOR map of transport_channel_identifier to channel_extra. AdvertisementSuffix exposes ble_psm() for the BLE channel (id 1). Unknown channel ids and out-of-range PSMs are ignored; a malformed suffix is logged and treated as absent without failing the advert. Both touched modules gain a module-scoped deny(indexing_slicing) and move to checked slice access.
…list The CTAP 2.3 hybrid draft assigns QR code key 6 to the array of data transfer channels the client supports (0 = WebSocket, 1 = BLE). The previous code used key 6 for a non-standard supports_non_discoverable_mc flag, which is not in the spec and is removed here. The list is set to [WebSocket] for now; BLE is added once the L2CAP channel is wired in.
Adds bluer (l2cap feature only, no D-Bus) and an L2capDataChannel implementing CableDataChannel over an insecure L2CAP CoC for PXP. CRLF message framing with a cancel-safe read buffer. Not yet wired into the connection flow, so the type is dead_code for now.
connection_stage now inspects the decrypted advertisement suffix: if it carries a BLE L2CAP PSM, it opens an L2capDataChannel to the peripheral discovered during the proximity check, falling back to the WebSocket tunnel if that fails. The QR code advertises both transports. Known devices stay on WebSocket, as state-assisted has no BLE channel negotiation in the CTAP 2.3 hybrid draft.
Adds a README feature line for the direct BLE L2CAP data channel, and updates the cable example wording since the channel is no longer always a tunnel. The example exercises CTAP 2.3 hybrid BLE unchanged: the QR code advertises BLE and connection_stage selects L2CAP when offered.
The kernel rejects BT_SECURITY level < LOW on L2CAP CoC sockets with -EINVAL (l2cap_sock_setsockopt), so set_security(Sdp) was failing the whole connect path. The default sec_level on a fresh socket is already BT_SECURITY_LOW, which on an LE link does not trigger pairing or encryption: the 'insecure' CoC the spec asks for. Switching to bluer::l2cap::Stream::connect also drops the redundant explicit bind, which it does internally.
bluer's poll_write silently caps writes to 16 bytes while BT_SNDMTU is not yet available, which happens for a brief window after connect() returns and before the LE Credit Based Connection Response is processed by the kernel. The ~80-byte Noise handshake then goes out as 5+ SDUs, which some peers don't reassemble before timing out. Poll send_mtu() (up to 2s, 50ms cadence) until the kernel has the peer's MTU, so the handshake is sent in a single SDU.
In caBLE v2 key 6 was a 'supports_non_discoverable_mc' boolean. The PXP draft repurposes it as the supported-transports array. Legacy clients (Google Play services Fido 26.18.x, observed via 'HybridAuthenticate- ChimeraActivity ... Expected a jcsv value, but got jcsw' on QR parse) hard-reject a CBOR array where they expect a bool, and crash the scan. Make the field Option<Vec<...>> and default to None; CTAP 2.3 hybrid-aware peers that need the new array semantics can opt in via the public field once they ship.
Per the caBLE v2 convention (cited by kanidm): Chrome omits this field when false; presence implies v2.1, absence implies v2.0. Emitting 'state_assisted = false' was claiming v2.1 with no state-assisted support, which is an unusual combination and the next plausible trigger for legacy GMS Fido's strict QR parser to reject.
CableQrCodeDevice::new_persistent and new_transient now take a CableTransports describing which transport channels the QR advertises: websocket_only (caBLE v2 legacy), ble_only, or websocket_and_ble. The set is a thin BTreeSet wrapper. Internally we collapse websocket_only to no key 6, since legacy parsers hard-reject a CBOR array at key 6 (where caBLE v2 expected a supports_non_discoverable_mc boolean). Anything that includes BLE opts in to the CTAP 2.3 hybrid transport-channel negotiation.
…info The state-assisted second leg of the cable example panicked with 'No known devices found' against any peer that didn't send linking info in its post-handshake message (GMS Fido for one). Detect the empty known-devices list and show a new QR for GetAssertion instead, so the example demonstrates a working two-ceremony flow regardless of the peer's state-assisted support.
The 'cable' name is reserved for an upcoming example that exercises both transports.
Transient MakeCredential only, CableTransports::CloudAssistedOrLocal. The QR offers both the WebSocket tunnel and the BLE L2CAP channel; the authenticator picks one (CTAP 2.3-aware peers may open L2CAP, caBLE v2 peers silently ignore key 6 and fall back to the WebSocket tunnel).
After a successful CTAP ceremony the peer typically tears down the BLE link without a clean L2CAP shutdown, so our read loop wakes up with ECONNRESET (or EPIPE / UnexpectedEof) rather than n=0. Treat those the same way we already treat a clean EOF: return Ok(None) when nothing is buffered, ConnectionLost when half a message is. Stops the spurious 'Failed to read L2CAP message: ConnectionReset' error log that fires after a successful MakeCredential.
CableQrCodeDevice's fields (NonZeroScalar, ByteArray, Option<Arc<dyn CableKnownDeviceInfoStore>>) all auto-derive Send + Sync, so the hand-rolled unsafe impls are dead. Verified compile-time via a T: Send + Sync assertion in the test module; downstream consumers (credentialsd spawns CableQrCodeDevice across tokio::spawn) keep working. Also brings the top-of-README transport-support table in line with the example table further down — caBLE v2 plus CTAP 2.3 hybrid.
75bf3b7 to
78214b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds support for the CTAP 2.3 hybrid transport over a direct BLE L2CAP channel, with no tunnel server.
Commits 1-2 refactor the Noise handshake and encrypted framing onto a transport-agnostic data-channel abstraction. The rest add advertisement-suffix parsing, the QR key 6 transport list, a
bluer-backed L2CAP data channel (l2cap feature only, no D-Bus), and L2CAP-or-WebSocket selection inconnection_stage.Scope is QR-initiated only. The draft has no way to negotiate a BLE channel for state-assisted transactions, so those stay on WebSocket.
Validated on Android 16 (Pixel 8 with Google Play Services). Checked for regressions on iOS.