Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ _Looking for the D-Bus API proposal?_ Check out [credentialsd][credentialsd].
- 🟢 Discoverable credentials (resident keys)
- 🟢 Hybrid transport (caBLE v2): QR-initiated transactions
- 🟢 Hybrid transport (caBLE v2): State-assisted transactions (remember this phone)
- 🟢 Hybrid transport (caBLE v2): Linger after the ceremony to capture the linking update
- 🟢 Hybrid transport (CTAP 2.3): direct BLE L2CAP data channel, QR-initiated, no tunnel server

## Runtime requirements
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ reqwest = { version = "0.12", default-features = false, features = [
[dev-dependencies]
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
qrcode = "0.14.1"
# test-util enables paused time for deterministic timeout/linger tests
tokio = { version = "1.45", features = ["test-util"] }
# For turning on logging in unittests
test-log = { version = "0.2" }

Expand Down
4 changes: 3 additions & 1 deletion libwebauthn/examples/ceremony/webauthn_cable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
//! MakeCredential only.
use std::error::Error;

use libwebauthn::transport::cable::is_available;
use libwebauthn::transport::cable::qr_code_device::{
CableQrCodeDevice, CableTransports, QrCodeOperationHint,
};
use libwebauthn::transport::cable::{is_available, CableClose};
use qrcode::render::unicode;
use qrcode::QrCode;

Expand Down Expand Up @@ -96,5 +96,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.expect("Failed to serialize MakeCredential response");
println!("WebAuthn MakeCredential response (JSON):\n{response_json}");

// A transient QR code never lingers, so this is a plain graceful close.
channel.close(CableClose::Immediate).await;
Ok(())
}
37 changes: 29 additions & 8 deletions libwebauthn/examples/ceremony/webauthn_cable_wss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ use std::error::Error;
use std::sync::Arc;
use std::time::Duration;

use libwebauthn::transport::cable::is_available;
use libwebauthn::transport::cable::known_devices::{
CableKnownDevice, ClientPayloadHint, EphemeralDeviceInfoStore,
};
use libwebauthn::transport::cable::qr_code_device::{
CableQrCodeDevice, CableTransports, QrCodeOperationHint,
};
use libwebauthn::transport::cable::{
is_available, CableClose, CableLingerConfig, CableLingerRegistry,
};
use qrcode::render::unicode;
use qrcode::QrCode;
use tokio::time::sleep;
Expand Down Expand Up @@ -68,6 +70,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
}

let device_info_store = Arc::new(EphemeralDeviceInfoStore::default());
// One registry per client, threaded through every hybrid channel. It lets
// a connection keep receiving the linking update after the ceremony, and
// a new connection evict the one still lingering.
let linger_registry = CableLingerRegistry::new();
let settings = || ChannelSettings {
cable_linger: Some(CableLingerConfig::new(linger_registry.clone())),
..Default::default()
};
let request_origin: RequestOrigin = "https://example.org".try_into().expect("Invalid origin");
let psl = SystemPublicSuffixList::auto().expect(
"PSL not available; install the publicsuffix-list (or publicsuffix-list-dafsa) package, or pass an explicit path",
Expand All @@ -89,7 +99,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.build();
println!("{}", image);

let mut channel = device.channel(ChannelSettings::default()).await.unwrap();
let mut channel = device.channel(settings()).await.unwrap();
println!("Channel established {:?}", channel);

let state_recv = channel.get_ux_update_receiver();
Expand All @@ -113,10 +123,18 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.to_json_string(&request, JsonFormat::Prettified)
.expect("Failed to serialize MakeCredential response");
println!("WebAuthn MakeCredential response (JSON):\n{response_json}");

// Say goodbye, then keep receiving in the background: the phone may
// send its linking information a while after the response.
channel.close(CableClose::Linger).await;
}

println!("Waiting for 5 seconds before contacting the device...");
println!("Waiting for 5 seconds for a linking update...");
sleep(Duration::from_secs(5)).await;
println!(
"Connections still lingering: {}",
linger_registry.lingering_count()
);

// Second leg: prefer state-assisted reconnection if the peer offered
// linking info, otherwise fall back to a fresh QR. Many authenticators
Expand All @@ -131,12 +149,11 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
)
.await
.unwrap();
let mut channel = known_device
.channel(ChannelSettings::default())
.await
.unwrap();
// Opening this channel evicts the lingering QR connection.
let mut channel = known_device.channel(settings()).await.unwrap();
println!("Channel established {:?}", channel);
run_get_assertion(&mut channel, &request_origin, &psl).await?;
channel.close(CableClose::Immediate).await;
} else {
println!("No known devices (peer did not offer linking). Falling back to QR.");
let mut device: CableQrCodeDevice = CableQrCodeDevice::new_persistent(
Expand All @@ -151,11 +168,15 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.light_color(unicode::Dense1x2::Dark)
.build();
println!("{}", image);
let mut channel = device.channel(ChannelSettings::default()).await.unwrap();
let mut channel = device.channel(settings()).await.unwrap();
println!("Channel established {:?}", channel);
run_get_assertion(&mut channel, &request_origin, &psl).await?;
// Nothing follows that could use a linking update, so just close.
channel.close(CableClose::Immediate).await;
}

// Signal any lingering connection to stop before the runtime goes away.
linger_registry.close_lingering();
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub async fn main() -> Result<(), WebAuthnError<HidError>> {
// token through it. The same settings apply to any transport.
let settings = ChannelSettings {
persistent_token_store: Some(store.clone()),
..Default::default()
};
let mut channel = device.channel(settings).await?;
let state_recv = channel.get_ux_update_receiver();
Expand Down
Loading
Loading