From a79d3ae0f56af148fe7fa86cd21918f8dc6d00c3 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Thu, 22 May 2025 21:40:15 +0200 Subject: [PATCH 1/4] Add nfc transport with pcsc backend --- libwebauthn/Cargo.toml | 5 + libwebauthn/src/transport/mod.rs | 2 + libwebauthn/src/transport/nfc/channel.rs | 295 ++++++++++++++++++++++ libwebauthn/src/transport/nfc/commands.rs | 87 +++++++ libwebauthn/src/transport/nfc/device.rs | 108 ++++++++ libwebauthn/src/transport/nfc/mod.rs | 35 +++ libwebauthn/src/transport/nfc/pcsc/mod.rs | 167 ++++++++++++ 7 files changed, 699 insertions(+) create mode 100644 libwebauthn/src/transport/nfc/channel.rs create mode 100644 libwebauthn/src/transport/nfc/commands.rs create mode 100644 libwebauthn/src/transport/nfc/device.rs create mode 100644 libwebauthn/src/transport/nfc/mod.rs create mode 100644 libwebauthn/src/transport/nfc/pcsc/mod.rs diff --git a/libwebauthn/Cargo.toml b/libwebauthn/Cargo.toml index 61705cae..8444e8ab 100644 --- a/libwebauthn/Cargo.toml +++ b/libwebauthn/Cargo.toml @@ -15,6 +15,8 @@ path = "src/lib.rs" [features] default = [] +nfc = ["apdu-core", "apdu"] +pcsc = [ "nfc", "dep:pcsc" ] [dependencies] base64-url = "3.0.0" @@ -64,6 +66,9 @@ snow = { version = "0.10", features = ["use-p256"] } ctap-types = { version = "0.4.0" } btleplug = "0.11.7" thiserror = "2.0.12" +apdu-core = { version = "0.4.0", optional = true } +apdu = { version = "0.4.0", optional = true } +pcsc = { version = "2.9.0", optional = true } [dev-dependencies] tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } diff --git a/libwebauthn/src/transport/mod.rs b/libwebauthn/src/transport/mod.rs index 63ed5f38..aa72f661 100644 --- a/libwebauthn/src/transport/mod.rs +++ b/libwebauthn/src/transport/mod.rs @@ -6,6 +6,8 @@ pub mod device; pub mod hid; #[cfg(test)] pub mod virt; +#[cfg(feature = "nfc")] +pub mod nfc; mod channel; mod transport; diff --git a/libwebauthn/src/transport/nfc/channel.rs b/libwebauthn/src/transport/nfc/channel.rs new file mode 100644 index 00000000..e0114fa4 --- /dev/null +++ b/libwebauthn/src/transport/nfc/channel.rs @@ -0,0 +1,295 @@ +use apdu::core::HandleError; +use apdu::{Command, Response, command}; +use apdu_core; +use async_trait::async_trait; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::time::Duration; +use tokio::sync::mpsc; +#[allow(unused_imports)] +use tracing::{Level, debug, instrument, trace, warn}; + +use crate::UxUpdate; +use crate::proto::ctap1::apdu::{ApduRequest, ApduResponse}; +use crate::proto::ctap2::cbor::{CborRequest, CborResponse}; +use crate::transport::channel::{AuthTokenData, Channel, ChannelStatus, Ctap2AuthTokenStore}; +use crate::transport::device::SupportedProtocols; +use crate::transport::error::{Error, TransportError}; + +use super::commands::{command_ctap_msg, command_get_response}; + +const SELECT_P1: u8 = 0x04; +const SELECT_P2: u8 = 0x00; +const APDU_FIDO: &[u8; 8] = b"\xa0\x00\x00\x06\x47\x2f\x00\x01"; +const SW1_MORE_DATA: u8 = 0x61; + +#[derive(thiserror::Error)] +pub enum NfcError { + /// APDU error returned by the card. + Apdu(#[from] apdu::Error), + + /// Unexpected error occurred on the device. + Device(Box), +} + +impl Debug for NfcError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for NfcError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NfcError::Apdu(e) => Display::fmt(e, f), + NfcError::Device(e) => e.fmt(f), + } + } +} + +impl From for Error { + fn from(input: NfcError) -> Self { + trace!("{:?}", input); + let output = match input { + NfcError::Apdu(_apdu_error) => TransportError::InvalidFraming, + NfcError::Device(_) => TransportError::ConnectionLost, + }; + Error::Transport(output) + } +} + +pub trait HandlerInCtx { + /// Handles the APDU command in a specific context. + /// Implementations must transmit the command to the card through a reader, + /// then receive the response from them, returning length of the data written. + fn handle_in_ctx(&mut self, ctx: Ctx, command: &[u8], response: &mut [u8]) + -> apdu_core::Result; +} + +pub trait NfcBackend: HandlerInCtx + Display {} + +pub struct NfcChannel +where + Ctx: Copy + Sync, +{ + delegate: Box + Send + Sync>, + auth_token_data: Option, + tx: mpsc::Sender, + ctx: Ctx, + apdu_response: Option, + cbor_response: Option, + supported: SupportedProtocols, + status: ChannelStatus, +} + +impl Display for NfcChannel +where + Ctx: Copy + Send + Sync, +{ + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "{}", self.delegate) + } +} + +impl NfcChannel +where + Ctx: fmt::Debug + Display + Copy + Send + Sync, +{ + pub fn new( + delegate: Box + Send + Sync>, + ctx: Ctx, + tx: mpsc::Sender, + ) -> Self { + NfcChannel { + delegate, + auth_token_data: None, + tx, + ctx, + apdu_response: None, + cbor_response: None, + supported: SupportedProtocols { + fido2: false, + u2f: false, + }, + status: ChannelStatus::Ready, + } + } + + #[instrument(skip_all)] + pub async fn wink(&mut self, _timeout: Duration) -> Result { + warn!("WINK capability is not supported"); + return Ok(false); + } + + pub fn select_fido2(&mut self) -> Result<(), Error> { + let command = command::select_file(SELECT_P1, SELECT_P2, APDU_FIDO); + let is_u2f_v2 = self.handle(self.ctx, command).map(|e| (e == b"U2F_V2"))?; + self.supported = SupportedProtocols { + u2f: is_u2f_v2, + // A CTAP authenticatorGetInfo should be issued to + // determine if the device supports CTAP2 or + // not. Assume it does for now. + fido2: true, + }; + + Ok(()) + } + + fn handle_in_ctx( + &mut self, + ctx: Ctx, + command_buf: &Vec, + buf: &mut [u8], + ) -> Result { + self.delegate + .handle_in_ctx(ctx, &command_buf, buf) + .map_err(|e| match e { + HandleError::NotEnoughBuffer(l) => { + NfcError::Device(Box::new(HandleError::NotEnoughBuffer(l))) + } + HandleError::Nfc(e) => NfcError::Device(e), + }) + } + + pub fn handle<'a>( + &'a mut self, + ctx: Ctx, + command: impl Into>, + ) -> Result, NfcError> { + let command = command.into(); + let command_buf = Vec::from(command); + + let mut buf = [0u8; 1024]; + let mut rapdu = Vec::new(); + + let len: usize = self.handle_in_ctx(ctx, &command_buf, &mut buf)? as usize; + let mut resp = Response::from(&buf[..len]); + + let (mut sw1, mut sw2) = resp.trailer; + rapdu.extend_from_slice(resp.payload); + + while sw1 == SW1_MORE_DATA { + let get_response_cmd = command_get_response(0x00, 0x00, sw2); + let get_response_buf = Vec::from(get_response_cmd); + let len = self.handle_in_ctx(ctx, &get_response_buf, &mut buf)?; + resp = Response::from(&buf[..len]); + (sw1, sw2) = resp.trailer; + rapdu.extend_from_slice(resp.payload); + } + + rapdu.extend_from_slice(&[sw1, sw2]); + Result::from(Response::from(rapdu.as_slice())) + .map(|p| p.to_vec()) + .map_err(|e| { + trace!("map_err {:?}", e); + apdu::Error::from(e).into() + }) + } +} + +#[async_trait] +impl<'a, Ctx> Channel for NfcChannel +where + Ctx: Copy + Send + Sync + fmt::Debug + Display, +{ + async fn supported_protocols(&self) -> Result { + Ok(self.supported) + } + + async fn status(&self) -> ChannelStatus { + self.status + } + + async fn close(&mut self) { + todo!("close") + } + + #[instrument(level = Level::DEBUG, skip_all)] + async fn apdu_send(&self, request: &ApduRequest, _timeout: Duration) -> Result<(), Error> { + todo!("apdu_send") + } + + #[instrument(level = Level::DEBUG, skip_all)] + async fn apdu_recv(&self, _timeout: Duration) -> Result { + todo!("apdu_recv") + } + + #[instrument(level = Level::DEBUG, skip_all)] + async fn cbor_send( + &mut self, + request: &CborRequest, + _timeout: std::time::Duration, + ) -> Result<(), Error> { + let data = &request.ctap_hid_data(); + let mut rest: &[u8] = data; + + while rest.len() > 250 { + let to_send = &rest[..250]; + rest = &rest[250..]; + let ctap_msg = command_ctap_msg(true, to_send); + let resp = self.handle(self.ctx, ctap_msg)?; + trace!("cbor_send has_more {:?} {:?}", to_send, resp); + } + + let ctap_msg = command_ctap_msg(false, rest); + let resp = self.handle(self.ctx, ctap_msg)?; + trace!("cbor_send {:?} {:?}", rest, resp); + + // FIXME check for SW_UPDATE? + + // let mut rapdu_buf = [0; pcsc::MAX_BUFFER_SIZE_EXTENDED]; + // let (mut resp, mut sw1, mut sw2) = self.card + // .chain_apdus(0x80, 0x10, 0x80, 0x00, data, &mut rapdu_buf) + // .expect("APDU exchange failed"); + + // loop { + // while (sw1, sw2) == SW_UPDATE { + // // ka_status = STATUS(resp[0]) + // // if on_keepalive and last_ka != ka_status: + // // last_ka = ka_status + // // on_keepalive(ka_status) + // // NFCCTAP_GETRESPONSE + + // (resp, sw1, sw2) = self.card + // .chain_apdus(0x80, 0x11, 0x00, 0x00, &[], &mut rapdu_buf).expect("APDU chained exchange failed"); + // debug!("Error {:?} {:?}", sw1, sw2); + // } + + // if (sw1, sw2) != SW_SUCCESS { + // return Err(Error::Transport(TransportError::InvalidFraming)); + // } + + let cbor_response = CborResponse::try_from(&resp) + .or(Err(Error::Transport(TransportError::InvalidFraming)))?; + self.cbor_response = Some(cbor_response); + Ok(()) + } + + #[instrument(level = Level::DEBUG, skip_all)] + async fn cbor_recv(&mut self, _timeout: std::time::Duration) -> Result { + self.cbor_response + .take() + .ok_or(Error::Transport(TransportError::InvalidFraming)) + } + + fn get_state_sender(&self) -> &mpsc::Sender { + &self.tx + } +} + +impl Ctap2AuthTokenStore for NfcChannel +where + Ctx: Copy + Send + Sync, +{ + fn store_auth_data(&mut self, auth_token_data: AuthTokenData) { + self.auth_token_data = Some(auth_token_data); + } + + fn get_auth_data(&self) -> Option<&AuthTokenData> { + self.auth_token_data.as_ref() + } + + fn clear_uv_auth_token_store(&mut self) { + self.auth_token_data = None; + } +} diff --git a/libwebauthn/src/transport/nfc/commands.rs b/libwebauthn/src/transport/nfc/commands.rs new file mode 100644 index 00000000..f2c8f2f4 --- /dev/null +++ b/libwebauthn/src/transport/nfc/commands.rs @@ -0,0 +1,87 @@ +use apdu::Command; + +// Copy private impl +const CLA_DEFAULT: u8 = 0x00; +const CLA_INTER_INDUSTRY: u8 = 0x80; + +macro_rules! impl_into_vec { + ($name: ty) => { + impl<'a> From<$name> for Vec { + fn from(cmd: $name) -> Self { + Command::from(cmd).into() + } + } + }; +} + +const INS_GET_RESPONSE: u8 = 0xC0; + +/// `GET RESPONSE` (0xC0) command. +#[derive(Debug)] +pub struct GetResponseCommand { + p1: u8, + p2: u8, + le: u8, +} + +impl GetResponseCommand { + /// Constructs a `GET RESPONSE` command. + pub fn new(p1: u8, p2: u8, le: u8) -> Self { + Self { p1, p2, le } + } +} + +impl<'a> From for Command<'a> { + fn from(cmd: GetResponseCommand) -> Self { + Self::new_with_le(CLA_DEFAULT, INS_GET_RESPONSE, cmd.p1, cmd.p2, cmd.le.into()) + } +} + +impl_into_vec!(GetResponseCommand); + +/// Constructs a `GET RESPONSE` command. +pub fn command_get_response(p1: u8, p2: u8, le: u8) -> GetResponseCommand { + GetResponseCommand::new(p1, p2, le) +} + +const CLA_HAS_MORE: u8 = 0x10; +const INS_CTAP_MSG: u8 = 0x10; +const CTAP_P1_SUPP_GET_RESP: u8 = 0x80; +const CTAP_P2: u8 = 0x00; + +/// `CTAP MSG` (0x10) command. +#[derive(Debug)] +pub struct CtapMsgCommand<'a> { + has_more: bool, + payload: &'a [u8], +} + +impl<'a> CtapMsgCommand<'a> { + /// Constructs a `CTAP MSG` command. + pub fn new(has_more: bool, payload: &'a [u8]) -> Self { + Self { has_more, payload } + } +} + +impl<'a> From> for Command<'a> { + fn from(cmd: CtapMsgCommand<'a>) -> Self { + let cla = match cmd.has_more { + true => CLA_HAS_MORE, + false => 0, + } | CLA_INTER_INDUSTRY; + Self::new_with_payload( + cla, + INS_CTAP_MSG, + 0, //CTAP_P1_SUPP_GET_RESP, + CTAP_P2, + cmd.payload, + ) + } +} + +impl_into_vec!(CtapMsgCommand<'a>); + +/// Constructs a `GET MSG` command. +pub fn command_ctap_msg(has_more: bool, payload: &[u8]) -> CtapMsgCommand { + CtapMsgCommand::new(has_more, payload) +} diff --git a/libwebauthn/src/transport/nfc/device.rs b/libwebauthn/src/transport/nfc/device.rs new file mode 100644 index 00000000..d4ce4320 --- /dev/null +++ b/libwebauthn/src/transport/nfc/device.rs @@ -0,0 +1,108 @@ +use async_trait::async_trait; +use std::fmt; +use tokio::sync::mpsc; +#[allow(unused_imports)] +use tracing::{debug, info, instrument, trace}; + +use crate::UxUpdate; +use crate::transport::device::Device; +use crate::transport::error::Error; + +use super::channel::NfcChannel; +#[cfg(feature = "pcsc")] +use super::pcsc; +use super::{Context, Nfc}; + +#[derive(Debug)] +enum DeviceInfo { + #[cfg(feature = "pcsc")] + Pcsc(pcsc::Info), +} + +#[derive(Debug)] +pub struct NfcDevice { + info: DeviceInfo, +} + +impl fmt::Display for DeviceInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + #[cfg(feature = "pcsc")] + DeviceInfo::Pcsc(info) => write!(f, "{}", info), + } + } +} + +impl fmt::Display for NfcDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.info) + } +} + +impl NfcDevice { + #[cfg(feature = "pcsc")] + pub fn new_pcsc(info: pcsc::Info) -> Self { + NfcDevice { + info: DeviceInfo::Pcsc(info), + } + } + + fn channel_sync<'d>( + &'d self, + ) -> Result<(NfcChannel, mpsc::Receiver), Error> { + trace!("nfc channel {:?}", self); + let (mut channel, recv): (NfcChannel, mpsc::Receiver) = match &self.info + { + #[cfg(feature = "pcsc")] + DeviceInfo::Pcsc(info) => info.channel(), + }?; + + channel.select_fido2()?; + + Ok((channel, recv)) + } +} + +#[async_trait] +impl<'d> Device<'d, Nfc, NfcChannel> for NfcDevice { + async fn channel( + &'d mut self, + ) -> Result<(NfcChannel, mpsc::Receiver), Error> { + self.channel_sync() + } +} + +fn is_fido(device: &NfcDevice) -> bool +where + Ctx: fmt::Debug + fmt::Display + Copy + Send + Sync, +{ + fn inner(device: &NfcDevice) -> Result + where + Ctx: fmt::Debug + fmt::Display + Copy + Send + Sync, + { + let (mut chan, _send) = device.channel_sync()?; + let _ = chan.select_fido2()?; + Ok(true) + } + + inner::(device).is_ok() +} + +#[instrument] +pub async fn list_devices() -> Result, Error> { + let mut all_devices = Vec::new(); + let list_devices_fns = [ + #[cfg(feature = "pcsc")] + pcsc::list_devices, + ]; + + for list_devices in list_devices_fns { + let mut devices = list_devices()? + .into_iter() + .filter(|e| is_fido::(&e)) + .collect::>(); + all_devices.append(&mut devices); + } + + Ok(all_devices) +} diff --git a/libwebauthn/src/transport/nfc/mod.rs b/libwebauthn/src/transport/nfc/mod.rs new file mode 100644 index 00000000..631e23be --- /dev/null +++ b/libwebauthn/src/transport/nfc/mod.rs @@ -0,0 +1,35 @@ +use std::fmt::{Display, Formatter}; + +pub mod channel; +pub mod commands; +pub mod device; +#[cfg(feature = "pcsc")] +pub mod pcsc; + +pub use device::list_devices; + +use super::Transport; + +pub struct Nfc {} +impl Transport for Nfc {} +unsafe impl Send for Nfc {} +unsafe impl Sync for Nfc {} + +impl Display for Nfc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "NFC") + } +} + +#[derive(Clone, Debug)] +pub struct Context {} + +impl Display for Context { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "context") + } +} + +unsafe impl Send for Context {} +unsafe impl Sync for Context {} +impl Copy for Context {} diff --git a/libwebauthn/src/transport/nfc/pcsc/mod.rs b/libwebauthn/src/transport/nfc/pcsc/mod.rs new file mode 100644 index 00000000..6876ab1d --- /dev/null +++ b/libwebauthn/src/transport/nfc/pcsc/mod.rs @@ -0,0 +1,167 @@ +use super::Context; +use super::channel::{HandlerInCtx, NfcBackend, NfcChannel}; +use super::device::NfcDevice; +use crate::UxUpdate; +use crate::transport::error::{Error, TransportError}; +use apdu::core::HandleError; +use pcsc; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::fmt::Debug; +use std::ops::Deref; +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc; +#[allow(unused_imports)] +use tracing::{debug, info, instrument, trace}; + +#[derive(Debug)] +pub struct Info { + name: CString, +} + +pub struct PcscCard { + pub card: Option, +} + +impl<'tx> Deref for PcscCard { + type Target = pcsc::Card; + + fn deref(&self) -> &pcsc::Card { + self.card.as_ref().unwrap() + } +} + +// By default pcsc resets the card but to be able to reconnect the +// card has to be powered down instead. +impl Drop for PcscCard { + fn drop(&mut self) { + let _ = PcscCard::disconnect(self.card.take()); + } +} + +impl PcscCard { + pub fn new(card: pcsc::Card) -> Self { + PcscCard { card: Some(card) } + } + + fn map_disconnect_error(pair: (pcsc::Card, pcsc::Error)) -> Error { + let (_card, _err) = pair; + Error::Transport(TransportError::InvalidFraming) + } + + fn disconnect(card: Option) -> Result<(), Error> { + match card { + Some(card) => { + debug!("Disconnect card"); + card.disconnect(pcsc::Disposition::UnpowerCard) + .map_err(PcscCard::map_disconnect_error) + } + None => Ok(()), + } + } +} + +pub struct Channel { + card: Arc>, +} + +unsafe impl Send for Channel {} + +impl fmt::Display for Info { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.name) + } +} + +impl From for Error { + fn from(input: pcsc::Error) -> Self { + trace!("{:?}", input); + let output = match input { + pcsc::Error::NoSmartcard => TransportError::ConnectionFailed, + _ => TransportError::InvalidFraming, + }; + + Error::Transport(output) + } +} + +impl Info { + pub fn new(name: &CStr) -> Self { + Info { + name: CStr::into_c_string(name.into()), + } + } + + pub fn channel(&self) -> Result<(NfcChannel, mpsc::Receiver), Error> { + let (send, recv) = mpsc::channel(1); + let context = pcsc::Context::establish(pcsc::Scope::User)?; + let chan = Channel::new(self, context)?; + + let ctx = Context {}; + let channel = NfcChannel::new(Box::new(chan), ctx, send); + Ok((channel, recv)) + } +} + +impl Channel { + pub fn new(info: &Info, context: pcsc::Context) -> Result { + let card = context.connect(&info.name, pcsc::ShareMode::Shared, pcsc::Protocols::ANY)?; + + let chan = Self { + card: Arc::new(Mutex::new(PcscCard::new(card))), + }; + + Ok(chan) + } +} + +impl fmt::Display for Channel { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + let card = self.card.lock().unwrap(); + let (names_len, atr_len) = card.status2_len().unwrap(); + let mut names_buf = vec![0; names_len]; + let mut atr_buf = vec![0; atr_len]; + let status = card.status2(&mut names_buf, &mut atr_buf).unwrap(); + write!(f, "{:?}", status.reader_names().collect::>()) + } +} + +impl NfcBackend for Channel where Ctx: fmt::Debug + fmt::Display {} + +impl HandlerInCtx for Channel +where + Ctx: fmt::Debug + fmt::Display, +{ + fn handle_in_ctx( + &mut self, + _ctx: Ctx, + command: &[u8], + response: &mut [u8], + ) -> apdu_core::Result { + trace!("TX: {:?}", command); + + let rapdu = self + .card + .lock() + .unwrap() + .transmit(command, response) + .map_err(|e| HandleError::Nfc(Box::new(e)))?; + + trace!("RX: {:?}", rapdu); + Ok(rapdu.len()) + } +} + +#[instrument] +pub fn list_devices() -> Result, Error> { + let ctx = pcsc::Context::establish(pcsc::Scope::User).expect("PC/SC context"); + let len = ctx.list_readers_len().expect("PC/SC readers len"); + let mut readers_buf = vec![0; len]; + let devices = ctx + .list_readers(&mut readers_buf) + .expect("PC/SC readers") + .map(|x| NfcDevice::new_pcsc(Info::new(x))) + .collect::>(); + + Ok(devices) +} From 31e147a4dc1ee57c948f778fa30c912b5a7845e0 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Thu, 22 May 2025 23:54:06 +0200 Subject: [PATCH 2/4] Add libnfc backend to nfc transport --- libwebauthn/Cargo.toml | 7 + libwebauthn/src/transport/nfc/device.rs | 17 ++ libwebauthn/src/transport/nfc/libnfc/mod.rs | 232 ++++++++++++++++++++ libwebauthn/src/transport/nfc/mod.rs | 2 + 4 files changed, 258 insertions(+) create mode 100644 libwebauthn/src/transport/nfc/libnfc/mod.rs diff --git a/libwebauthn/Cargo.toml b/libwebauthn/Cargo.toml index 8444e8ab..2b2c0eb5 100644 --- a/libwebauthn/Cargo.toml +++ b/libwebauthn/Cargo.toml @@ -17,6 +17,11 @@ path = "src/lib.rs" default = [] nfc = ["apdu-core", "apdu"] pcsc = [ "nfc", "dep:pcsc" ] +libnfc = [ + "nfc", + "nfc1-sys", + "nfc1", +] [dependencies] base64-url = "3.0.0" @@ -69,6 +74,8 @@ thiserror = "2.0.12" apdu-core = { version = "0.4.0", optional = true } apdu = { version = "0.4.0", optional = true } pcsc = { version = "2.9.0", optional = true } +nfc1 = { version = "0.6.0", optional = true, default-features = false } +nfc1-sys = { version = "0.3.9", optional = true, default-features = false } [dev-dependencies] tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } diff --git a/libwebauthn/src/transport/nfc/device.rs b/libwebauthn/src/transport/nfc/device.rs index d4ce4320..36a53952 100644 --- a/libwebauthn/src/transport/nfc/device.rs +++ b/libwebauthn/src/transport/nfc/device.rs @@ -9,12 +9,16 @@ use crate::transport::device::Device; use crate::transport::error::Error; use super::channel::NfcChannel; +#[cfg(feature = "libnfc")] +use super::libnfc; #[cfg(feature = "pcsc")] use super::pcsc; use super::{Context, Nfc}; #[derive(Debug)] enum DeviceInfo { + #[cfg(feature = "libnfc")] + LibNfc(libnfc::Info), #[cfg(feature = "pcsc")] Pcsc(pcsc::Info), } @@ -27,6 +31,8 @@ pub struct NfcDevice { impl fmt::Display for DeviceInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { + #[cfg(feature = "libnfc")] + DeviceInfo::LibNfc(info) => write!(f, "{}", info), #[cfg(feature = "pcsc")] DeviceInfo::Pcsc(info) => write!(f, "{}", info), } @@ -40,6 +46,13 @@ impl fmt::Display for NfcDevice { } impl NfcDevice { + #[cfg(feature = "libnfc")] + pub fn new_libnfc(info: libnfc::Info) -> Self { + NfcDevice { + info: DeviceInfo::LibNfc(info), + } + } + #[cfg(feature = "pcsc")] pub fn new_pcsc(info: pcsc::Info) -> Self { NfcDevice { @@ -53,6 +66,8 @@ impl NfcDevice { trace!("nfc channel {:?}", self); let (mut channel, recv): (NfcChannel, mpsc::Receiver) = match &self.info { + #[cfg(feature = "libnfc")] + DeviceInfo::LibNfc(info) => info.channel(), #[cfg(feature = "pcsc")] DeviceInfo::Pcsc(info) => info.channel(), }?; @@ -92,6 +107,8 @@ where pub async fn list_devices() -> Result, Error> { let mut all_devices = Vec::new(); let list_devices_fns = [ + #[cfg(feature = "libnfc")] + libnfc::list_devices, #[cfg(feature = "pcsc")] pcsc::list_devices, ]; diff --git a/libwebauthn/src/transport/nfc/libnfc/mod.rs b/libwebauthn/src/transport/nfc/libnfc/mod.rs new file mode 100644 index 00000000..6abeae5f --- /dev/null +++ b/libwebauthn/src/transport/nfc/libnfc/mod.rs @@ -0,0 +1,232 @@ +use super::Context; +use super::channel::{HandlerInCtx, NfcBackend, NfcChannel}; +use super::device::NfcDevice; +use crate::UxUpdate; +use crate::transport::error::{Error, TransportError}; +use apdu::core::HandleError; +use apdu_core; +use std::fmt; +use std::fmt::Debug; +use std::io::Write; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; +use tokio::sync::mpsc; +#[allow(unused_imports)] +use tracing::{debug, info, instrument, trace}; + +const MAX_DEVICES: usize = 10; +const TIMEOUT: Duration = Duration::from_millis(5000); +const MODULATION_TYPE: nfc1::ModulationType = nfc1::ModulationType::Iso14443a; + +#[derive(Debug)] +pub struct Info { + connstring: String, +} + +impl fmt::Display for Info { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.connstring) + } +} + +fn map_error(_err: nfc1::Error) -> Error { + Error::Transport(TransportError::ConnectionFailed) +} + +impl From for Error { + fn from(input: nfc1::Error) -> Self { + trace!("{:?}", input); + let output = match input { + // rs-nfc1 errors + nfc1::Error::Malloc => TransportError::TransportUnavailable, + nfc1::Error::Undefined(_c_int) => TransportError::TransportUnavailable, + nfc1::Error::UndefinedModulationType => TransportError::TransportUnavailable, + nfc1::Error::NoDeviceFound => TransportError::TransportUnavailable, + + // libnfc errors + nfc1::Error::Io => TransportError::ConnectionLost, + nfc1::Error::InvalidArgument => TransportError::NegotiationFailed, + nfc1::Error::DeviceNotSupported => TransportError::InvalidEndpoint, + nfc1::Error::NoSuchDeviceFound => TransportError::InvalidEndpoint, + nfc1::Error::BufferOverflow => TransportError::InvalidFraming, + nfc1::Error::Timeout => TransportError::Timeout, + nfc1::Error::OperationAborted => TransportError::InvalidFraming, + nfc1::Error::NotImplemented => TransportError::NegotiationFailed, + nfc1::Error::TargetReleased => TransportError::NegotiationFailed, + nfc1::Error::RfTransmissionError => TransportError::NegotiationFailed, + nfc1::Error::MifareAuthFailed => TransportError::NegotiationFailed, + nfc1::Error::Soft => TransportError::Timeout, + nfc1::Error::Chip => TransportError::InvalidFraming, + }; + Error::Transport(output) + } +} + +impl Info { + pub fn new(connstring: &String) -> Self { + Info { + connstring: connstring.clone(), + } + } + + pub fn channel(&self) -> Result<(NfcChannel, mpsc::Receiver), Error> { + let (send, recv) = mpsc::channel(1); + let context = nfc1::Context::new().map_err(|e| map_error(e))?; + + let mut chan = Channel::new(self, context); + + { + let mut device = chan.device.lock().unwrap(); + device.initiator_init()?; + device.set_property_bool(nfc1::Property::InfiniteSelect, false)?; + + let info = device.get_information_about()?; + debug!("Info: {}", info); + } + + let target = chan.connect_to_target()?; + debug!("Selected: {:?}", target); + + let ctx = Context {}; + let channel = NfcChannel::new(Box::new(chan), ctx, send); + Ok((channel, recv)) + } +} + +pub struct Channel { + device: Arc>, +} + +unsafe impl Send for Channel {} + +impl Channel { + pub fn new(info: &Info, mut context: nfc1::Context) -> Self { + let device = context + .open_with_connstring(&info.connstring) + .expect("opened device"); + + Self { + device: Arc::new(Mutex::new(device)), + } + } + + fn initiator_select_passive_target_ex( + device: &mut nfc1::Device, + modulation: &nfc1::Modulation, + ) -> nfc1::Result { + match device.initiator_select_passive_target(&modulation) { + Ok(target) => { + if let nfc1::target_info::TargetInfo::Iso14443a(iso) = target.target_info { + if iso.uid_len > 0 { + Ok(target) + } else { + Err(nfc1::Error::NoDeviceFound) + } + } else { + Err(nfc1::Error::NoDeviceFound) + } + } + Err(err) => { + println!("Error: {}", err); + Err(err) + } + } + } + + fn connect_to_target(&mut self) -> Result { + let mut device = self.device.lock().unwrap(); + // Assume baudrates are already sorted higher to lower + let baudrates = device.get_supported_baud_rate(nfc1::Mode::Initiator, MODULATION_TYPE)?; + let modulations = baudrates + .iter() + .map(|baud_rate| nfc1::Modulation { + modulation_type: MODULATION_TYPE, + baud_rate: *baud_rate, + }) + .collect::>(); + let modulation = &modulations[modulations.len() - 1]; + let is_one_rate = modulations.len() == 1; + for i in 0..2 { + if i > 0 { + thread::sleep(Duration::from_millis(100)); + } + trace!("Poll {:?} {}", modulation, i); + if let Ok(target) = + Channel::initiator_select_passive_target_ex(&mut device, &modulation) + { + if is_one_rate { + return Ok(target); + } + + for modulation in modulations.iter() { + device.initiator_deselect_target()?; + device.initiator_init()?; + trace!("Try {:?}", modulation); + if let Ok(target) = + Channel::initiator_select_passive_target_ex(&mut device, &modulation) + { + return Ok(target); + } + } + } + } + + Err(Error::Transport(TransportError::TransportUnavailable)) + } +} + +impl HandlerInCtx for Channel +where + Ctx: fmt::Debug + fmt::Display, +{ + fn handle_in_ctx( + &mut self, + _ctx: Ctx, + command: &[u8], + mut response: &mut [u8], + ) -> apdu_core::Result { + let timeout = nfc1::Timeout::Duration(TIMEOUT); + let len = response.len(); + trace!("TX: {:?}", command); + let rapdu = self + .device + .lock() + .unwrap() + .initiator_transceive_bytes(command, len, timeout) + .map_err(|e| HandleError::Nfc(Box::new(e)))?; + + trace!("RX: {:?}", rapdu); + + if response.len() < rapdu.len() { + return Err(HandleError::NotEnoughBuffer(rapdu.len())); + } + + response + .write(&rapdu) + .map_err(|e| HandleError::Nfc(Box::new(e))) + } +} + +impl fmt::Display for Channel { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + let mut device = self.device.lock().unwrap(); + write!(f, "{}", device.name()) + } +} + +impl NfcBackend for Channel where Ctx: fmt::Debug + fmt::Display {} + +#[instrument] +pub fn list_devices() -> Result, Error> { + let mut context = + nfc1::Context::new().map_err(|_| Error::Transport(TransportError::TransportUnavailable))?; + let devices = context + .list_devices(MAX_DEVICES) + .expect("libnfc devices") + .iter() + .map(|x| NfcDevice::new_libnfc(Info::new(x))) + .collect::>(); + + Ok(devices) +} diff --git a/libwebauthn/src/transport/nfc/mod.rs b/libwebauthn/src/transport/nfc/mod.rs index 631e23be..cc94bb6a 100644 --- a/libwebauthn/src/transport/nfc/mod.rs +++ b/libwebauthn/src/transport/nfc/mod.rs @@ -3,6 +3,8 @@ use std::fmt::{Display, Formatter}; pub mod channel; pub mod commands; pub mod device; +#[cfg(feature = "libnfc")] +pub mod libnfc; #[cfg(feature = "pcsc")] pub mod pcsc; From f5fbced9c5c1ded9284f0126a1be1ff3708e17b9 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Tue, 30 Sep 2025 22:32:48 +0200 Subject: [PATCH 3/4] Adapt nfc transport to API changes --- libwebauthn/src/transport/nfc/channel.rs | 47 ++++++++++++++++----- libwebauthn/src/transport/nfc/commands.rs | 2 +- libwebauthn/src/transport/nfc/device.rs | 14 +++--- libwebauthn/src/transport/nfc/libnfc/mod.rs | 12 +++--- libwebauthn/src/transport/nfc/pcsc/mod.rs | 12 +++--- 5 files changed, 53 insertions(+), 34 deletions(-) diff --git a/libwebauthn/src/transport/nfc/channel.rs b/libwebauthn/src/transport/nfc/channel.rs index e0114fa4..a7a0f398 100644 --- a/libwebauthn/src/transport/nfc/channel.rs +++ b/libwebauthn/src/transport/nfc/channel.rs @@ -5,16 +5,18 @@ use async_trait::async_trait; use std::fmt; use std::fmt::{Debug, Display, Formatter}; use std::time::Duration; -use tokio::sync::mpsc; +use tokio::sync::broadcast; +use tokio::sync::mpsc::{self, Sender}; #[allow(unused_imports)] use tracing::{Level, debug, instrument, trace, warn}; -use crate::UxUpdate; +use crate::webauthn::error::Error; +use crate::UvUpdate; use crate::proto::ctap1::apdu::{ApduRequest, ApduResponse}; use crate::proto::ctap2::cbor::{CborRequest, CborResponse}; use crate::transport::channel::{AuthTokenData, Channel, ChannelStatus, Ctap2AuthTokenStore}; use crate::transport::device::SupportedProtocols; -use crate::transport::error::{Error, TransportError}; +use crate::transport::error::TransportError; use super::commands::{command_ctap_msg, command_get_response}; @@ -23,6 +25,8 @@ const SELECT_P2: u8 = 0x00; const APDU_FIDO: &[u8; 8] = b"\xa0\x00\x00\x06\x47\x2f\x00\x01"; const SW1_MORE_DATA: u8 = 0x61; +pub type CancelNfcOperation = (); + #[derive(thiserror::Error)] pub enum NfcError { /// APDU error returned by the card. @@ -68,15 +72,27 @@ pub trait HandlerInCtx { pub trait NfcBackend: HandlerInCtx + Display {} +#[derive(Debug, Clone)] +pub struct NfcChannelHandle { + tx: Sender, +} + +impl NfcChannelHandle { + pub async fn cancel_ongoing_operation(&self) { + let _ = self.tx.send(()).await; + } +} + pub struct NfcChannel where Ctx: Copy + Sync, { delegate: Box + Send + Sync>, auth_token_data: Option, - tx: mpsc::Sender, + ux_update_sender: broadcast::Sender, + handle: NfcChannelHandle, ctx: Ctx, - apdu_response: Option, + _apdu_response: Option, cbor_response: Option, supported: SupportedProtocols, status: ChannelStatus, @@ -98,14 +114,17 @@ where pub fn new( delegate: Box + Send + Sync>, ctx: Ctx, - tx: mpsc::Sender, ) -> Self { + let (ux_update_sender, _) = broadcast::channel(16); + let (handle_tx, _handle_rx) = mpsc::channel(1); + let handle = NfcChannelHandle { tx: handle_tx }; NfcChannel { delegate, auth_token_data: None, - tx, + ux_update_sender, + handle, ctx, - apdu_response: None, + _apdu_response: None, cbor_response: None, supported: SupportedProtocols { fido2: false, @@ -115,6 +134,10 @@ where } } + pub fn get_handle(&self) -> NfcChannelHandle { + self.handle.clone() + } + #[instrument(skip_all)] pub async fn wink(&mut self, _timeout: Duration) -> Result { warn!("WINK capability is not supported"); @@ -192,6 +215,8 @@ impl<'a, Ctx> Channel for NfcChannel where Ctx: Copy + Send + Sync + fmt::Debug + Display, { + type UxUpdate = UvUpdate; + async fn supported_protocols(&self) -> Result { Ok(self.supported) } @@ -205,7 +230,7 @@ where } #[instrument(level = Level::DEBUG, skip_all)] - async fn apdu_send(&self, request: &ApduRequest, _timeout: Duration) -> Result<(), Error> { + async fn apdu_send(&self, _request: &ApduRequest, _timeout: Duration) -> Result<(), Error> { todo!("apdu_send") } @@ -272,8 +297,8 @@ where .ok_or(Error::Transport(TransportError::InvalidFraming)) } - fn get_state_sender(&self) -> &mpsc::Sender { - &self.tx + fn get_ux_update_sender(&self) -> &broadcast::Sender { + &self.ux_update_sender } } diff --git a/libwebauthn/src/transport/nfc/commands.rs b/libwebauthn/src/transport/nfc/commands.rs index f2c8f2f4..50a35e93 100644 --- a/libwebauthn/src/transport/nfc/commands.rs +++ b/libwebauthn/src/transport/nfc/commands.rs @@ -46,7 +46,7 @@ pub fn command_get_response(p1: u8, p2: u8, le: u8) -> GetResponseCommand { const CLA_HAS_MORE: u8 = 0x10; const INS_CTAP_MSG: u8 = 0x10; -const CTAP_P1_SUPP_GET_RESP: u8 = 0x80; +const _CTAP_P1_SUPP_GET_RESP: u8 = 0x80; const CTAP_P2: u8 = 0x00; /// `CTAP MSG` (0x10) command. diff --git a/libwebauthn/src/transport/nfc/device.rs b/libwebauthn/src/transport/nfc/device.rs index 36a53952..d0c8a8e6 100644 --- a/libwebauthn/src/transport/nfc/device.rs +++ b/libwebauthn/src/transport/nfc/device.rs @@ -1,12 +1,10 @@ use async_trait::async_trait; use std::fmt; -use tokio::sync::mpsc; #[allow(unused_imports)] use tracing::{debug, info, instrument, trace}; -use crate::UxUpdate; +use crate::webauthn::error::Error; use crate::transport::device::Device; -use crate::transport::error::Error; use super::channel::NfcChannel; #[cfg(feature = "libnfc")] @@ -62,9 +60,9 @@ impl NfcDevice { fn channel_sync<'d>( &'d self, - ) -> Result<(NfcChannel, mpsc::Receiver), Error> { + ) -> Result, Error> { trace!("nfc channel {:?}", self); - let (mut channel, recv): (NfcChannel, mpsc::Receiver) = match &self.info + let mut channel: NfcChannel = match &self.info { #[cfg(feature = "libnfc")] DeviceInfo::LibNfc(info) => info.channel(), @@ -74,7 +72,7 @@ impl NfcDevice { channel.select_fido2()?; - Ok((channel, recv)) + Ok(channel) } } @@ -82,7 +80,7 @@ impl NfcDevice { impl<'d> Device<'d, Nfc, NfcChannel> for NfcDevice { async fn channel( &'d mut self, - ) -> Result<(NfcChannel, mpsc::Receiver), Error> { + ) -> Result, Error> { self.channel_sync() } } @@ -95,7 +93,7 @@ where where Ctx: fmt::Debug + fmt::Display + Copy + Send + Sync, { - let (mut chan, _send) = device.channel_sync()?; + let mut chan = device.channel_sync()?; let _ = chan.select_fido2()?; Ok(true) } diff --git a/libwebauthn/src/transport/nfc/libnfc/mod.rs b/libwebauthn/src/transport/nfc/libnfc/mod.rs index 6abeae5f..26fb794e 100644 --- a/libwebauthn/src/transport/nfc/libnfc/mod.rs +++ b/libwebauthn/src/transport/nfc/libnfc/mod.rs @@ -1,8 +1,8 @@ use super::Context; use super::channel::{HandlerInCtx, NfcBackend, NfcChannel}; use super::device::NfcDevice; -use crate::UxUpdate; -use crate::transport::error::{Error, TransportError}; +use crate::webauthn::error::Error; +use crate::transport::error::TransportError; use apdu::core::HandleError; use apdu_core; use std::fmt; @@ -11,7 +11,6 @@ use std::io::Write; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use tokio::sync::mpsc; #[allow(unused_imports)] use tracing::{debug, info, instrument, trace}; @@ -70,8 +69,7 @@ impl Info { } } - pub fn channel(&self) -> Result<(NfcChannel, mpsc::Receiver), Error> { - let (send, recv) = mpsc::channel(1); + pub fn channel(&self) -> Result, Error> { let context = nfc1::Context::new().map_err(|e| map_error(e))?; let mut chan = Channel::new(self, context); @@ -89,8 +87,8 @@ impl Info { debug!("Selected: {:?}", target); let ctx = Context {}; - let channel = NfcChannel::new(Box::new(chan), ctx, send); - Ok((channel, recv)) + let channel = NfcChannel::new(Box::new(chan), ctx); + Ok(channel) } } diff --git a/libwebauthn/src/transport/nfc/pcsc/mod.rs b/libwebauthn/src/transport/nfc/pcsc/mod.rs index 6876ab1d..d0b0aa95 100644 --- a/libwebauthn/src/transport/nfc/pcsc/mod.rs +++ b/libwebauthn/src/transport/nfc/pcsc/mod.rs @@ -1,8 +1,8 @@ use super::Context; use super::channel::{HandlerInCtx, NfcBackend, NfcChannel}; use super::device::NfcDevice; -use crate::UxUpdate; -use crate::transport::error::{Error, TransportError}; +use crate::webauthn::error::Error; +use crate::transport::error::TransportError; use apdu::core::HandleError; use pcsc; use std::ffi::{CStr, CString}; @@ -10,7 +10,6 @@ use std::fmt; use std::fmt::Debug; use std::ops::Deref; use std::sync::{Arc, Mutex}; -use tokio::sync::mpsc; #[allow(unused_imports)] use tracing::{debug, info, instrument, trace}; @@ -92,14 +91,13 @@ impl Info { } } - pub fn channel(&self) -> Result<(NfcChannel, mpsc::Receiver), Error> { - let (send, recv) = mpsc::channel(1); + pub fn channel(&self) -> Result, Error> { let context = pcsc::Context::establish(pcsc::Scope::User)?; let chan = Channel::new(self, context)?; let ctx = Context {}; - let channel = NfcChannel::new(Box::new(chan), ctx, send); - Ok((channel, recv)) + let channel = NfcChannel::new(Box::new(chan), ctx); + Ok(channel) } } From 59d559061a69bdf4a0d4a0c35379d763cc05ac53 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Wed, 21 May 2025 23:50:25 +0200 Subject: [PATCH 4/4] examples: Implement Nfc in Hid tests u2f_hid webauthn_hid webauthn_preflight_hid: --- libwebauthn/examples/u2f_hid.rs | 3 +++ libwebauthn/examples/webauthn_hid.rs | 3 +++ libwebauthn/examples/webauthn_preflight_hid.rs | 7 +++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/libwebauthn/examples/u2f_hid.rs b/libwebauthn/examples/u2f_hid.rs index 58be0e22..1026ff30 100644 --- a/libwebauthn/examples/u2f_hid.rs +++ b/libwebauthn/examples/u2f_hid.rs @@ -6,7 +6,10 @@ use tokio::sync::broadcast::Receiver; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::u2f::{RegisterRequest, SignRequest}; +#[cfg(not(feature = "nfc"))] use libwebauthn::transport::hid::list_devices; +#[cfg(feature = "nfc")] +use libwebauthn::transport::nfc::list_devices; use libwebauthn::transport::{Channel as _, Device}; use libwebauthn::u2f::U2F; diff --git a/libwebauthn/examples/webauthn_hid.rs b/libwebauthn/examples/webauthn_hid.rs index b5bdb484..d2aadcbd 100644 --- a/libwebauthn/examples/webauthn_hid.rs +++ b/libwebauthn/examples/webauthn_hid.rs @@ -17,7 +17,10 @@ use libwebauthn::proto::ctap2::{ Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity, Ctap2PublicKeyCredentialUserEntity, }; +#[cfg(not(feature = "nfc"))] use libwebauthn::transport::hid::list_devices; +#[cfg(feature = "nfc")] +use libwebauthn::transport::nfc::list_devices; use libwebauthn::transport::{Channel as _, Device}; use libwebauthn::webauthn::{Error as WebAuthnError, WebAuthn}; diff --git a/libwebauthn/examples/webauthn_preflight_hid.rs b/libwebauthn/examples/webauthn_preflight_hid.rs index bc54eee4..7daf9fe3 100644 --- a/libwebauthn/examples/webauthn_preflight_hid.rs +++ b/libwebauthn/examples/webauthn_preflight_hid.rs @@ -20,7 +20,10 @@ use libwebauthn::proto::ctap2::{ Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity, Ctap2PublicKeyCredentialType, Ctap2PublicKeyCredentialUserEntity, }; +#[cfg(not(feature = "nfc"))] use libwebauthn::transport::hid::list_devices; +#[cfg(feature = "nfc")] +use libwebauthn::transport::nfc::list_devices; use libwebauthn::transport::{Channel as _, Device}; use libwebauthn::webauthn::{CtapError, Error as WebAuthnError, WebAuthn}; @@ -156,7 +159,7 @@ pub async fn main() -> Result<(), Box> { } async fn make_credential_call( - channel: &mut HidChannel<'_>, + channel: &mut impl Channel, user_id: &[u8], exclude_list: Option>, ) -> Result { @@ -194,7 +197,7 @@ async fn make_credential_call( } async fn get_assertion_call( - channel: &mut HidChannel<'_>, + channel: &mut impl Channel, allow_list: Vec, ) -> Result { let challenge: [u8; 32] = thread_rng().gen();