Skip to content

Fix #52: QR-initiated hybrid transport support on Android#97

Merged
AlfioEmanueleFresta merged 6 commits into
masterfrom
cable-fix
May 6, 2025
Merged

Fix #52: QR-initiated hybrid transport support on Android#97
AlfioEmanueleFresta merged 6 commits into
masterfrom
cable-fix

Conversation

@AlfioEmanueleFresta
Copy link
Copy Markdown
Member

@AlfioEmanueleFresta AlfioEmanueleFresta commented May 5, 2025

Changes

See list of commits for individual changes.

Testing

Tested successfully with a Pixel 8 Pro running Android 15.

Creation & assertion are successful using Bitwarden on the phone.

Motivation

I was originally getting a HTTP 400 error upon connecting to Google's caBLE servers:

DEBUG libwebauthn::transport::cable::tunnel: Connecting to tunnel server connect_url="wss://cable.ua5v.com/cable/connect/078f8a/4daa63321c9414caecee728764b0b3c9"
DEBUG rustls::anchors: add_parsable_certificates processed 148 valid and 0 invalid certs    
DEBUG tokio_tungstenite::tls::encryption::rustls: Added 148/148 native root certificates (ignored 0)    
DEBUG rustls::client::hs: No cached session for DnsName("cable.ua5v.com")    
DEBUG rustls::client::hs: Not resuming any session    
DEBUG rustls::client::hs: Using ciphersuite TLS13_AES_256_GCM_SHA384    
DEBUG rustls::client::tls13: Not resuming    
DEBUG rustls::client::tls13: TLS1.3 encrypted extensions: []    
DEBUG rustls::client::hs: ALPN protocol is None    
ERROR libwebauthn::transport::cable::tunnel: Failed to connect to tunnel server e=Http(Response { status: 400, version: HTTP/1.1, headers: {"content-length": "1451", "content-security-policy-report-only": "script-src 'none'; form-action 'none'; frame-src 'none'; report-uri https://csp.withgoogle.com/csp/goa-55573edb", "content-type": "text/html; charset=utf-8", "cross-origin-opener-policy-report-only": "same-origin; report-to=\"goa-55573edb\"", "report-to": "{\"group\":\"goa-55573edb\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/goa-55573edb\"}]}", "x-content-type-options": "nosniff", "x-frame-options": "SAMEORIGIN", "x-xss-protection": "0", "date": "Mon, 05 May 2025 10:40:43 GMT", "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", "connection": "close"}, body: Some([10, 60, 33, 68, 79, 67, 84, 89, 80, 69, 32, 104, 116, 109, 108, 62, 10, 60, 104, 116, 109, 108, 32, 108, 97, 110, 103, 61, 101, 110, 62, 10, 32, 32, 60, 109, 101, 116, 97, ...]) })

thread 'main' panicked at libwebauthn/examples/webauthn_cable.rs:98:60:
called `Result::unwrap()` on an `Err` value: Transport(ConnectionFailed)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This was fixed by setting the fido.cable subprotocol on the WSS request.

Then, we were failing to decode the CBOR payload received over the tunnel:

TRACE libwebauthn::transport::cable::tunnel: Trimmed padding decrypted_frame=[...] decrypted_frame_len=97
ERROR libwebauthn::transport::cable::tunnel: Failed to decode initial message e=SerdeDeCustom
ERROR libwebauthn::transport::cable::tunnel: Failed to process initial message e=Transport(ConnectionFailed)

thread 'main' panicked at libwebauthn/examples/webauthn_cable.rs:136:6:
called `Result::unwrap()` on an `Err` value: Transport(TransportUnavailable)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This was caused by missing fields in CableInitialMessage, which now includes a list of features (CTAP and digital credentials) as strings

Once added, I was getting an error for missing supported PI/UV auth protocols:

DEBUG webauthn_make_credential{dev=CableChannel}:user_verification:ctap2_get_info: libwebauthn::proto::ctap2::protocol: CTAP2 GetInfo successful
TRACE webauthn_make_credential{dev=CableChannel}:user_verification:ctap2_get_info: libwebauthn::proto::ctap2::protocol: ctap_response=Ctap2GetInfoResponse { versions: ["FIDO_2_0", "FIDO_2_1"], extensions: Some(["prf"]), aaguid: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], options: Some({"up": true, "rk": true, "plat": false, "uv": true}), max_msg_size: None, pin_auth_protos: None, max_credential_count: None, max_credential_id_length: None, transports: Some(["internal", "hybrid"]), algorithms: None, max_blob_array: None, force_pin_change: None, min_pin_length: None, firmware_version: None, max_cred_blob_length: None, max_rpids_for_setminpinlength: None, preferred_platform_uv_attempts: None, uv_modality: None, certifications: None, remaining_discoverable_creds: None, vendor_proto_config_cmds: None, attestation_formats: None, uv_count_since_last_pin_entry: None, long_touch_for_reset: None, enc_identifier: None, transports_for_reset: None, pin_complexity_policy: None, pin_complexity_policy_url: None, max_pin_length: None }
ERROR webauthn_make_credential{dev=CableChannel}:user_verification: libwebauthn::webauthn: No supported PIN/UV auth protocols found get_info_response.pin_auth_protos=None

thread 'main' panicked at libwebauthn/examples/webauthn_cable.rs:136:6:
called `Result::unwrap()` on an `Err` value: Ctap(Other)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

So I modified select_uv_proto to allow empty an empty GetInfo pinUvAuthProtocols field if no UV is required.

Now, the connection would get interrupted midway through the transaction. Checking adb logcat, I could see this is due to #95:

05-05 13:48:22.616  7975  3808 E Fido    : bfvv: the data in the message can't be decoded into CBOR
05-05 13:48:22.616  7975  3808 E Fido    : 	at bfvm.f(:com.google.android.gms@251633035@25.16.33 (260400-750736914):2416)
05-05 13:48:22.616  7975  3808 E Fido    : 	at bgty.a(:com.google.android.gms@251633035@25.16.33 (260400-750736914):235)
05-05 13:48:22.616  7975  3808 E Fido    : 	at bgtr.run(:com.google.android.gms@251633035@25.16.33 (260400-750736914):640)
05-05 13:48:22.616  7975  3808 E Fido    : 	at java.lang.Thread.run(Thread.java:1012)
05-05 13:48:22.616  7975  3808 E Fido    : Caused by: gqbb: Error in decoding CborValue from bytes
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.b(:com.google.android.gms@251633035@25.16.33 (260400-750736914):355)
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.a(:com.google.android.gms@251633035@25.16.33 (260400-750736914):1)
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbi.q(:com.google.android.gms@251633035@25.16.33 (260400-750736914):19)
05-05 13:48:22.616  7975  3808 E Fido    : 	at bfvm.f(:com.google.android.gms@251633035@25.16.33 (260400-750736914):1318)
05-05 13:48:22.616  7975  3808 E Fido    : 	... 3 more
05-05 13:48:22.616  7975  3808 E Fido    : Caused by: gqbb: Error in decoding CborValue from bytes
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.b(:com.google.android.gms@251633035@25.16.33 (260400-750736914):355)
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.b(:com.google.android.gms@251633035@25.16.33 (260400-750736914):243)
05-05 13:48:22.616  7975  3808 E Fido    : 	... 6 more
05-05 13:48:22.616  7975  3808 E Fido    : Caused by: gqbb: Error in decoding CborValue from bytes
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.b(:com.google.android.gms@251633035@25.16.33 (260400-750736914):355)
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.b(:com.google.android.gms@251633035@25.16.33 (260400-750736914):305)
05-05 13:48:22.616  7975  3808 E Fido    : 	... 7 more
05-05 13:48:22.616  7975  3808 E Fido    : Caused by: gqax: Keys in CBOR Map not in strictly ascending natural order:
05-05 13:48:22.616  7975  3808 E Fido    : Previous key: "type"
05-05 13:48:22.616  7975  3808 E Fido    : Current key: "alg"
05-05 13:48:22.616  7975  3808 E Fido    : 	at gqbj.b(:com.google.android.gms@251633035@25.16.33 (260400-750736914):237)
05-05 13:48:22.616  7975  3808 E Fido    : 	... 8 more

So I rebased on top of master which includes fix #96.

Then UX was encountering request timeouts, so I updated the code to support user-defined timeout values.

Finally, pre-flight requests were timing out because Android shows UI for pre-flight requests. These are not supported, as they should be silent.

ERROR webauthn_get_assertion{dev=CableChannel}:ctap2_get_assertion: libwebauthn::transport::cable::channel: CBOR response recv timeout elapsed=deadline has elapsed timeout=2s
DEBUG webauthn_get_assertion{dev=CableChannel}: libwebauthn::proto::ctap2::preflight: Pre-flight: Filtering out Ctap2PublicKeyCredentialDescriptor { id: [233, 220, 252, 82, 44, 76, 57, 246, 46, 170, 235, 207, 111, 237, 29, 118], type: PublicKey, transports: None }, because of error: Transport(Timeout)
 INFO webauthn_get_assertion{dev=CableChannel}: libwebauthn::proto::ctap2::preflight: Credential list AFTER preflight: []
 WARN webauthn_get_assertion{dev=CableChannel}: libwebauthn::webauthn: Preflight removed all credentials from the allow-list. Sending dummy request and erroring out.

As pre-flights are an optional operation, they are now skipped for Hybrid transport authenticators.

@AlfioEmanueleFresta AlfioEmanueleFresta changed the title Set fido.cable subprotocol on WebSocket connection [WiP] Fix #52: QR-initiated hybrid transport support on Android May 5, 2025
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes QR‑initiated hybrid transport support on Android by addressing connection and CBOR decoding errors, improving error handling for missing PIN/UV protocols, and updating timeout values.

  • Fixes HTTP 400 errors by setting the fido.cable subprotocol on WSS requests (tunnel changes).
  • Updates user verification flow to allow empty PIN/UV protocol fields and adjusts pre‑flight request handling.
  • Increases timeout for example usage and updates README documentation accordingly.

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
libwebauthn/src/webauthn.rs Refactored error handling and conditional pre‑flight support in various CTAP2 request flows.
libwebauthn/src/transport/channel.rs Added a default supports_preflight implementation and improved timeout error logs.
libwebauthn/src/transport/cable/tunnel.rs Sets the Sec‑WebSocket‑Protocol header using a modified client request.
libwebauthn/src/transport/cable/channel.rs Improved error logging in send/recv and disabled pre‑flight support for CableChannel.
libwebauthn/src/proto/ctap2/protocol.rs Replaced hardcoded timeout constants with function parameters for CTAP2 commands.
libwebauthn/src/pin.rs Updated select_uv_proto usage to handle missing protocols gracefully.
libwebauthn/examples/webauthn_cable.rs Adjusted timeout values in the example to accommodate longer operations.
README.md Updated documentation to reflect revised hybrid transport support.

Comment on lines +99 to 102
warn!(?get_info_response.pin_auth_protos, "No supported PIN/UV auth protocols found");
None
}

Copy link

Copilot AI May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider extracting the warning for unsupported PIN/UV auth protocols into a helper to avoid duplication across multiple call sites.

Suggested change
warn!(?get_info_response.pin_auth_protos, "No supported PIN/UV auth protocols found");
None
}
log_unsupported_pin_uv_protocols(&get_info_response.pin_auth_protos);
None
}
fn log_unsupported_pin_uv_protocols(pin_auth_protos: &Option<Vec<u8>>) {
warn!(?pin_auth_protos, "No supported PIN/UV auth protocols found");
}

Copilot uses AI. Check for mistakes.
trace!(?request);
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
self.cbor_send(&request.into(), timeout).await?;
Copy link

Copilot AI May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify that all instances of the previously hardcoded TIMEOUT_GET_INFO have been consistently updated to use the dynamic timeout parameter.

Copilot uses AI. Check for mistakes.
Comment thread libwebauthn/src/pin.rs
Comment on lines +394 to +396
let Some(uv_proto) = select_uv_proto(&get_info_response).await else {
error!("No supported PIN/UV auth protocols found");
return Err(Error::Ctap(CtapError::Other));
Copy link

Copilot AI May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Double-check that the updated error handling for select_uv_proto (returning an Option) is consistent with its usage in similar code blocks across the codebase.

Suggested change
let Some(uv_proto) = select_uv_proto(&get_info_response).await else {
error!("No supported PIN/UV auth protocols found");
return Err(Error::Ctap(CtapError::Other));
let uv_proto = match select_uv_proto(&get_info_response).await {
Some(proto) => proto,
None => {
error!("No supported PIN/UV auth protocols found");
return Err(Error::Ctap(CtapError::Other));
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

@msirringhaus msirringhaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I added one suggestion, but we can also just leave it as is.

Comment thread libwebauthn/src/webauthn.rs
@AlfioEmanueleFresta AlfioEmanueleFresta merged commit e36387a into master May 6, 2025
4 checks passed
@AlfioEmanueleFresta AlfioEmanueleFresta deleted the cable-fix branch May 6, 2025 07:02
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.

3 participants