From ac899e63038cae9cdd1ac481832eccb4f4770137 Mon Sep 17 00:00:00 2001 From: Alfie Fresta Date: Mon, 15 Jun 2026 22:35:10 +0100 Subject: [PATCH 1/2] chore(deps): bump libwebauthn to 0.8.0 Adapt to the new request prepare() and ChannelSettings APIs. --- Cargo.lock | 78 ++++++++++++++++++- credentialsd/Cargo.toml | 2 +- credentialsd/src/credential_service/hybrid.rs | 4 +- credentialsd/src/credential_service/nfc.rs | 4 +- credentialsd/src/credential_service/usb.rs | 6 +- credentialsd/src/gateway/mod.rs | 9 ++- credentialsd/src/gateway/util.rs | 41 +++++----- credentialsd/src/webauthn.rs | 2 +- 8 files changed, 113 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5299f46..110f190b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -773,6 +779,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "credentialsd" version = "0.2.0" @@ -862,6 +877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -992,6 +1008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "der_derive", "pem-rfc7468", "zeroize", ] @@ -1010,6 +1027,17 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.116", +] + [[package]] name = "deranged" version = "0.5.6" @@ -1210,6 +1238,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -2109,11 +2147,12 @@ dependencies = [ [[package]] name = "libwebauthn" -version = "0.5.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c49abf1fc00b4ef5f4324e8d868d35680a5aef3e56cc6402ad555236fff2ded" +checksum = "9d84c2a7c3401e02dbc3d117807bedc851dd7250d53f3e24365c78d45e3f372b" dependencies = [ "aes", + "aes-gcm", "apdu", "apdu-core", "async-trait", @@ -2127,12 +2166,15 @@ dependencies = [ "ctap-types", "curve25519-dalek", "dbus", + "der", + "flate2", "futures", "heapless", "hex", "hidapi", "hkdf", "hmac", + "http", "idna", "maplit", "mockall", @@ -2155,6 +2197,7 @@ dependencies = [ "serde_repr", "sha2", "snow", + "spki", "text_io", "thiserror 2.0.18", "time", @@ -2166,6 +2209,7 @@ dependencies = [ "url", "uuid", "x509-parser", + "zeroize", ] [[package]] @@ -2253,6 +2297,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -3306,6 +3360,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "slab" version = "0.4.12" @@ -4679,6 +4739,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.116", +] [[package]] name = "zerotrie" diff --git a/credentialsd/Cargo.toml b/credentialsd/Cargo.toml index 4ff62699..31ea22d0 100644 --- a/credentialsd/Cargo.toml +++ b/credentialsd/Cargo.toml @@ -13,7 +13,7 @@ credentialsd-common = { path = "../credentialsd-common" } futures = "0.3.32" futures-lite.workspace = true libc.workspace = true -libwebauthn = { version = "0.5.1", features = ["nfc-backend-libnfc", "nfc-backend-pcsc"] } +libwebauthn = { version = "0.8.0", features = ["nfc-backend-libnfc", "nfc-backend-pcsc"] } # 0.6.1 fails to build with non-vendored library. # https://github.com/alexrsagen/rs-nfc1/issues/15 nfc1 = { version = "=0.6.0", default-features = false } diff --git a/credentialsd/src/credential_service/hybrid.rs b/credentialsd/src/credential_service/hybrid.rs index ef2c68fe..d7e50760 100644 --- a/credentialsd/src/credential_service/hybrid.rs +++ b/credentialsd/src/credential_service/hybrid.rs @@ -12,7 +12,7 @@ use libwebauthn::transport::cable::channel::{CableUpdate, CableUxUpdate}; use libwebauthn::transport::cable::qr_code_device::{ CableQrCodeDevice, CableTransports, QrCodeOperationHint, }; -use libwebauthn::transport::{Channel, Device}; +use libwebauthn::transport::{Channel, ChannelSettings, Device}; use libwebauthn::webauthn::{Error as WebAuthnError, WebAuthn}; use credentialsd_common::model::Error; @@ -68,7 +68,7 @@ impl HybridHandler for InternalHybridHandler { return; }; tokio::spawn(async move { - let mut channel = match device.channel().await { + let mut channel = match device.channel(ChannelSettings::default()).await { Ok(channel) => channel, Err(e) => { tracing::error!("Failed to open hybrid channel: {:?}", e); diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index fad55a95..d3579925 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -6,7 +6,7 @@ use futures_lite::Stream; use libwebauthn::{ ops::webauthn::GetAssertionResponse, proto::CtapError, - transport::{nfc::device::NfcDevice, Channel, Device}, + transport::{nfc::device::NfcDevice, Channel, ChannelSettings, Device}, webauthn::{Error as WebAuthnError, WebAuthn}, UvUpdate, }; @@ -204,7 +204,7 @@ async fn handle_events( signal_tx: &Sender>, ) { let device_debug = device.to_string(); - match device.channel().await { + match device.channel(ChannelSettings::default()).await { Err(err) => { tracing::error!("Failed to open channel to NFC authenticator, cannot receive user verification events: {:?}", err); } diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index 378116b9..dec298e5 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -8,7 +8,7 @@ use libwebauthn::{ proto::CtapError, transport::{ hid::{channel::HidChannelHandle, HidDevice}, - Channel, Device, + Channel, ChannelSettings, Device, }, webauthn::{Error as WebAuthnError, WebAuthn}, UvUpdate, @@ -85,7 +85,7 @@ impl InProcessUsbHandler { tokio::spawn(async move { let dev = device.clone(); - let res = match device.channel().await { + let res = match device.channel(ChannelSettings::default()).await { Ok(ref mut channel) => { let cancel_handle = channel.get_handle(); stx.send((idx, dev, cancel_handle)).await.unwrap(); @@ -290,7 +290,7 @@ async fn handle_events( signal_tx: &Sender>, ) { let device_debug = device.to_string(); - match device.channel().await { + match device.channel(ChannelSettings::default()).await { Err(err) => { tracing::error!("Failed to open channel to USB authenticator, cannot receive user verification events: {:?}", err); } diff --git a/credentialsd/src/gateway/mod.rs b/credentialsd/src/gateway/mod.rs index d4ec4386..7f7d05f6 100644 --- a/credentialsd/src/gateway/mod.rs +++ b/credentialsd/src/gateway/mod.rs @@ -97,6 +97,7 @@ impl GatewayService { // - fail if not supported, or if RP ID doesn't match any related origins. let make_cred_request = create_credential_request_try_into_ctap2(&request, &request_environment) + .await .inspect_err(|_| { tracing::error!( "Could not parse passkey creation request. Rejecting request." @@ -162,12 +163,12 @@ impl GatewayService { // - query for related origins, if supported // - fail if not supported, or if RP ID doesn't match any related origins. let get_cred_request = - get_credential_request_try_into_ctap2(&request, &request_environment).map_err( - |e| { + get_credential_request_try_into_ctap2(&request, &request_environment) + .await + .map_err(|e| { tracing::error!("Could not parse passkey assertion request: {e:?}"); WebAuthnError::TypeError - }, - )?; + })?; let cred_request = CredentialRequest::GetPublicKeyCredentialRequest(get_cred_request.clone()); diff --git a/credentialsd/src/gateway/util.rs b/credentialsd/src/gateway/util.rs index 42d9c6e1..bb0732d3 100644 --- a/credentialsd/src/gateway/util.rs +++ b/credentialsd/src/gateway/util.rs @@ -13,11 +13,11 @@ use libwebauthn::ops::webauthn::idl::origin::{ Origin as LibwebauthnOrigin, RequestOrigin as LibwebauthnRequestOrigin, }; use libwebauthn::ops::webauthn::psl::SystemPublicSuffixList; +use libwebauthn::ops::webauthn::{OriginValidation, RelatedOrigins, RequestSettings}; use crate::model::{GetAssertionResponseInternal, MakeCredentialResponseInternal}; use crate::webauthn::{ - GetAssertionRequest, MakeCredentialRequest, NavigationContext, Origin, WebAuthnIDL, - WebAuthnIDLResponse, + GetAssertionRequest, MakeCredentialRequest, NavigationContext, Origin, WebAuthnIDLResponse, }; impl TryFrom<&Origin> for LibwebauthnOrigin { @@ -55,11 +55,17 @@ fn load_system_psl() -> Result { }) } +fn request_settings(psl: &SystemPublicSuffixList) -> RequestSettings<'_> { + RequestSettings { + origin: OriginValidation::Validate { + public_suffix_list: psl, + related_origins: RelatedOrigins::Disabled, + }, + } +} + /// Parses a WebAuthn create credential request from D-Bus into a CTAP2 MakeCredentialRequest. -/// -/// Uses libwebauthn's `WebAuthnIDL::from_json()` for parsing. The relying party ID is derived -/// from the request's origin; libwebauthn validates that any rpId in the JSON matches it. -pub(super) fn create_credential_request_try_into_ctap2( +pub(super) async fn create_credential_request_try_into_ctap2( request: &CreateCredentialRequest, request_environment: &NavigationContext, ) -> std::result::Result { @@ -70,14 +76,15 @@ pub(super) fn create_credential_request_try_into_ctap2( let request_origin: LibwebauthnRequestOrigin = request_environment.try_into()?; let psl = load_system_psl()?; + let settings = request_settings(&psl); let make_cred_request = - MakeCredentialRequest::from_json(&request_origin, &psl, &options.request_json).map_err( - |err| { + MakeCredentialRequest::prepare(&request_origin, &options.request_json, &settings) + .await + .map_err(|err| { tracing::info!("Failed to parse MakeCredential request JSON: {err}"); WebAuthnError::TypeError - }, - )?; + })?; Ok(make_cred_request) } @@ -110,10 +117,7 @@ pub(super) fn create_credential_response_try_from_ctap2( } /// Parses a WebAuthn get credential request from D-Bus into a CTAP2 GetAssertionRequest. -/// -/// Uses libwebauthn's `WebAuthnIDL::from_json()` for parsing. The relying party ID is derived -/// from the request's origin; libwebauthn validates that any rpId in the JSON matches it. -pub(super) fn get_credential_request_try_into_ctap2( +pub(super) async fn get_credential_request_try_into_ctap2( request: &GetCredentialRequest, request_environment: &NavigationContext, ) -> std::result::Result { @@ -124,14 +128,15 @@ pub(super) fn get_credential_request_try_into_ctap2( let request_origin: LibwebauthnRequestOrigin = request_environment.try_into()?; let psl = load_system_psl()?; + let settings = request_settings(&psl); let get_assertion_request = - GetAssertionRequest::from_json(&request_origin, &psl, &options.request_json).map_err( - |err| { + GetAssertionRequest::prepare(&request_origin, &options.request_json, &settings) + .await + .map_err(|err| { tracing::info!("Failed to parse GetAssertion request JSON: {err}"); WebAuthnError::TypeError - }, - )?; + })?; Ok(get_assertion_request) } diff --git a/credentialsd/src/webauthn.rs b/credentialsd/src/webauthn.rs index 8fc4f880..85754d4c 100644 --- a/credentialsd/src/webauthn.rs +++ b/credentialsd/src/webauthn.rs @@ -4,7 +4,7 @@ use std::{fmt::Display, str::FromStr}; pub use libwebauthn::ops::webauthn::{ - GetAssertionRequest, MakeCredentialRequest, RelyingPartyId, WebAuthnIDL, WebAuthnIDLResponse, + GetAssertionRequest, MakeCredentialRequest, RelyingPartyId, WebAuthnIDLResponse, }; /// An application ID conforming to the From 794123fc2f9a8abbc80299f6eec1e7406140a851 Mon Sep 17 00:00:00 2001 From: Alfie Fresta Date: Mon, 15 Jun 2026 22:35:56 +0100 Subject: [PATCH 2/2] feat: reuse pinUvAuthToken across security-key ceremonies Wire a shared in-memory persistent token store into the USB and NFC ceremony channels. --- credentialsd/src/credential_service/mod.rs | 11 ++++++++++- credentialsd/src/credential_service/nfc.rs | 7 ++++++- credentialsd/src/credential_service/usb.rs | 7 ++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index 42b97285..8a232ea9 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -5,12 +5,13 @@ pub mod usb; use std::{ fmt::Debug, pin::Pin, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, OnceLock}, task::Poll, }; use async_trait::async_trait; use futures_lite::{FutureExt, Stream, StreamExt}; +use libwebauthn::pin::persistent_token::{MemoryPersistentTokenStore, PersistentTokenStore}; use libwebauthn::{ self, ops::webauthn::{GetAssertionResponse, MakeCredentialResponse}, @@ -35,6 +36,14 @@ use self::{ pub use usb::UsbState; +/// Process-wide in-memory store so a security key's pinUvAuthToken is reused across ceremonies. +fn persistent_token_store() -> Arc { + static STORE: OnceLock> = OnceLock::new(); + STORE + .get_or_init(|| Arc::new(MemoryPersistentTokenStore::new())) + .clone() +} + #[derive(Debug)] struct RequestContext { request: CredentialRequest, diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index d3579925..33411c54 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -204,7 +204,12 @@ async fn handle_events( signal_tx: &Sender>, ) { let device_debug = device.to_string(); - match device.channel(ChannelSettings::default()).await { + match device + .channel(ChannelSettings { + persistent_token_store: Some(super::persistent_token_store()), + }) + .await + { Err(err) => { tracing::error!("Failed to open channel to NFC authenticator, cannot receive user verification events: {:?}", err); } diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index dec298e5..70e1524e 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -290,7 +290,12 @@ async fn handle_events( signal_tx: &Sender>, ) { let device_debug = device.to_string(); - match device.channel(ChannelSettings::default()).await { + match device + .channel(ChannelSettings { + persistent_token_store: Some(super::persistent_token_store()), + }) + .await + { Err(err) => { tracing::error!("Failed to open channel to USB authenticator, cannot receive user verification events: {:?}", err); }