Skip to content

Commit fd614e2

Browse files
feat(cable): add webauthn_cable example exercising 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).
1 parent ba7b618 commit fd614e2

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ WebAuthn examples consume and emit JSON per the [WebAuthn IDL][webauthn].
7373
| **USB (HID)** | `cargo run --example u2f_hid` | `cargo run --example webauthn_hid` |
7474
| **Bluetooth (BLE)** | `cargo run --example u2f_ble` ||
7575
| **NFC** [^nfc] | `cargo run --features nfc-backend-pcsc --example u2f_nfc`<br>`cargo run --features nfc-backend-libnfc --example u2f_nfc` | `cargo run --features nfc-backend-pcsc --example webauthn_nfc`<br>`cargo run --features nfc-backend-libnfc --example webauthn_nfc` |
76+
| **Hybrid (caBLE v2 + CTAP 2.3)** || `cargo run --example webauthn_cable` |
7677
| **Hybrid (caBLE v2)** || `cargo run --example webauthn_cable_wss` |
7778

7879
[^nfc]: `nfc-backend-pcsc` is pure userspace and recommended on most systems. `nfc-backend-libnfc` requires the `libnfc` system library. Both can be enabled together; the first FIDO device found by either backend is used.

libwebauthn/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ name = "webauthn_nfc"
122122
path = "examples/ceremony/webauthn_nfc.rs"
123123
required-features = ["nfc"]
124124

125+
[[example]]
126+
name = "webauthn_cable"
127+
path = "examples/ceremony/webauthn_cable.rs"
128+
125129
[[example]]
126130
name = "webauthn_cable_wss"
127131
path = "examples/ceremony/webauthn_cable_wss.rs"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//! caBLE / CTAP 2.3 hybrid: the QR advertises both the WebSocket tunnel and
2+
//! the BLE L2CAP channel so the authenticator can pick either one. Transient
3+
//! MakeCredential only.
4+
use std::error::Error;
5+
6+
use libwebauthn::transport::cable::is_available;
7+
use libwebauthn::transport::cable::qr_code_device::{
8+
CableQrCodeDevice, CableTransports, QrCodeOperationHint,
9+
};
10+
use qrcode::render::unicode;
11+
use qrcode::QrCode;
12+
13+
use libwebauthn::ops::webauthn::{
14+
DatFilePublicSuffixList, JsonFormat, MakeCredentialRequest, RequestOrigin, WebAuthnIDL as _,
15+
WebAuthnIDLResponse as _,
16+
};
17+
use libwebauthn::transport::{Channel as _, Device};
18+
use libwebauthn::webauthn::WebAuthn;
19+
20+
#[path = "../common/mod.rs"]
21+
mod common;
22+
23+
const MAKE_CREDENTIAL_REQUEST: &str = r#"
24+
{
25+
"rp": {
26+
"id": "example.org",
27+
"name": "Example Relying Party"
28+
},
29+
"user": {
30+
"id": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2Nzg",
31+
"name": "Mario Rossi",
32+
"displayName": "Mario Rossi"
33+
},
34+
"challenge": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2Nzg",
35+
"pubKeyCredParams": [
36+
{"type": "public-key", "alg": -7}
37+
],
38+
"timeout": 120000,
39+
"excludeCredentials": [],
40+
"authenticatorSelection": {
41+
"residentKey": "discouraged",
42+
"userVerification": "preferred"
43+
},
44+
"attestation": "none"
45+
}
46+
"#;
47+
48+
#[tokio::main]
49+
pub async fn main() -> Result<(), Box<dyn Error>> {
50+
common::setup_logging();
51+
52+
if !is_available().await {
53+
eprintln!("No Bluetooth adapter found. Cable/Hybrid transport is unavailable.");
54+
return Err("Cable transport not available".into());
55+
}
56+
57+
let request_origin: RequestOrigin = "https://example.org".try_into().expect("Invalid origin");
58+
let psl = DatFilePublicSuffixList::from_system_file().expect(
59+
"PSL not available; install the publicsuffix-list package or pass an explicit path",
60+
);
61+
62+
let mut device: CableQrCodeDevice = CableQrCodeDevice::new_transient(
63+
QrCodeOperationHint::MakeCredential,
64+
CableTransports::CloudAssistedOrLocal,
65+
)?;
66+
67+
println!("Created QR code, awaiting for advertisement.");
68+
let qr_code = QrCode::new(device.qr_code.to_string()).unwrap();
69+
let image = qr_code
70+
.render::<unicode::Dense1x2>()
71+
.dark_color(unicode::Dense1x2::Light)
72+
.light_color(unicode::Dense1x2::Dark)
73+
.build();
74+
println!("{}", image);
75+
76+
let mut channel = device.channel().await.unwrap();
77+
println!("Channel established {:?}", channel);
78+
79+
let state_recv = channel.get_ux_update_receiver();
80+
tokio::spawn(common::handle_cable_updates(state_recv));
81+
82+
let request = MakeCredentialRequest::from_json(&request_origin, &psl, MAKE_CREDENTIAL_REQUEST)
83+
.expect("Failed to parse request JSON");
84+
85+
let response = retry_user_errors!(channel.webauthn_make_credential(&request)).unwrap();
86+
let response_json = response
87+
.to_json_string(&request, JsonFormat::Prettified)
88+
.expect("Failed to serialize MakeCredential response");
89+
println!("WebAuthn MakeCredential response (JSON):\n{response_json}");
90+
91+
Ok(())
92+
}

0 commit comments

Comments
 (0)