Skip to content

Commit 9462f83

Browse files
committed
Add NFC
1 parent 0e17121 commit 9462f83

File tree

20 files changed

+1566
-136
lines changed

20 files changed

+1566
-136
lines changed

credentialsd-common/Cargo.lock

Lines changed: 150 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

credentialsd-common/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ license = "LGPL-3.0-only"
77

88
[dependencies]
99
futures-lite = "2.6.0"
10-
libwebauthn = "0.2"
10+
# libwebauthn = "0.2"
11+
libwebauthn = { path = "../../libwebauthn/libwebauthn", features = ["libnfc"] }
1112
serde = { version = "1", features = ["derive"] }
1213
zvariant = "5.6.0"

credentialsd-common/src/client.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub trait FlowController {
1515

1616
fn get_hybrid_credential(&mut self) -> impl Future<Output = Result<(), ()>> + Send;
1717
fn get_usb_credential(&mut self) -> impl Future<Output = Result<(), ()>> + Send;
18+
fn get_nfc_credential(&mut self) -> impl Future<Output = Result<(), ()>> + Send;
1819
fn subscribe(
1920
&mut self,
2021
) -> impl Future<

credentialsd-common/src/model.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ impl Transport {
170170
}
171171
}
172172

173-
#[derive(Serialize, Deserialize)]
173+
#[derive(Debug, Clone, Serialize, Deserialize)]
174174
pub enum ViewUpdate {
175175
SetTitle(String),
176176
SetDevices(Vec<Device>),
@@ -183,6 +183,9 @@ pub enum ViewUpdate {
183183
UsbNeedsUserVerification { attempts_left: Option<u32> },
184184
UsbNeedsUserPresence,
185185

186+
NfcNeedsPin { attempts_left: Option<u32> },
187+
NfcNeedsUserVerification { attempts_left: Option<u32> },
188+
186189
HybridNeedsQrCode(String),
187190
HybridConnecting,
188191
HybridConnected,
@@ -262,10 +265,46 @@ pub enum UsbState {
262265
Failed(Error),
263266
}
264267

268+
/// Used to share public state between credential service and UI.
269+
#[derive(Clone, Debug, Default)]
270+
pub enum NfcState {
271+
/// Not polling for FIDO USB device.
272+
#[default]
273+
Idle,
274+
275+
/// Awaiting FIDO USB device to be plugged in.
276+
Waiting,
277+
278+
/// USB device connected, prompt user to tap
279+
Connected,
280+
281+
/// The device needs the PIN to be entered.
282+
NeedsPin { attempts_left: Option<u32> },
283+
284+
/// The device needs on-device user verification.
285+
NeedsUserVerification { attempts_left: Option<u32> },
286+
287+
// TODO: implement cancellation
288+
// This isn't actually sent from the server.
289+
//UserCancelled,
290+
/// Multiple credentials have been found and the user has to select which to use
291+
SelectCredential {
292+
/// List of user-identities to decide which to use.
293+
creds: Vec<Credential>,
294+
},
295+
296+
/// USB tapped, received credential
297+
Completed,
298+
299+
/// Interaction with the authenticator failed.
300+
Failed(Error),
301+
}
302+
265303
#[derive(Clone, Debug)]
266304
pub enum BackgroundEvent {
267305
UsbStateChanged(UsbState),
268306
HybridQrStateChanged(HybridState),
307+
NfcStateChanged(NfcState),
269308
}
270309

271310
#[derive(Debug, Clone)]

credentialsd-common/src/server.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ impl From<&BackgroundEvent> for Structure<'_> {
2828
BackgroundEvent::HybridQrStateChanged(state) => {
2929
tag_value_to_struct(0x02, Some(Value::Structure(state.into())))
3030
}
31+
BackgroundEvent::NfcStateChanged(state) => {
32+
tag_value_to_struct(0x03, Some(Value::Structure(state.into())))
33+
}
3134
}
3235
}
3336
}
@@ -49,6 +52,10 @@ impl TryFrom<&Structure<'_>> for BackgroundEvent {
4952
(&structure).try_into()?,
5053
))
5154
}
55+
0x03 => {
56+
let structure: Structure = value.downcast_ref()?;
57+
Ok(BackgroundEvent::NfcStateChanged((&structure).try_into()?))
58+
}
5259
_ => Err(zvariant::Error::Message(format!(
5360
"Unknown BackgroundEvent tag : {tag}"
5461
))),
@@ -336,6 +343,10 @@ impl Type for crate::model::UsbState {
336343
const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE;
337344
}
338345

346+
impl Type for crate::model::NfcState {
347+
const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE;
348+
}
349+
339350
impl From<&crate::model::UsbState> for Structure<'_> {
340351
fn from(value: &crate::model::UsbState) -> Self {
341352
let (tag, value): (u8, Option<Value>) = match value {
@@ -462,6 +473,128 @@ impl<'de> Deserialize<'de> for crate::model::UsbState {
462473
}
463474
}
464475

476+
impl From<&crate::model::NfcState> for Structure<'_> {
477+
fn from(value: &crate::model::NfcState) -> Self {
478+
let (tag, value): (u8, Option<Value>) = match value {
479+
crate::model::NfcState::Idle => (0x01, None),
480+
crate::model::NfcState::Waiting => (0x02, None),
481+
crate::model::NfcState::Connected => (0x04, None),
482+
// TODO: Add pin request reason to this struct
483+
crate::model::NfcState::NeedsPin { attempts_left } => {
484+
let num = match attempts_left {
485+
Some(num) => *num as i32,
486+
None => -1,
487+
};
488+
(0x05, Some(Value::I32(num)))
489+
}
490+
crate::model::NfcState::NeedsUserVerification { attempts_left } => {
491+
let num = match attempts_left {
492+
Some(num) => *num as i32,
493+
None => -1,
494+
};
495+
(0x06, Some(Value::I32(num)))
496+
}
497+
crate::model::NfcState::SelectCredential { creds } => {
498+
let creds: Vec<Credential> = creds.iter().map(Credential::from).collect();
499+
let value = Value::new(creds);
500+
(0x08, Some(value))
501+
}
502+
crate::model::NfcState::Completed => (0x09, None),
503+
crate::model::NfcState::Failed(error) => {
504+
let value = Value::<'_>::from(error.to_string());
505+
(0x0A, Some(value))
506+
}
507+
};
508+
tag_value_to_struct(tag, value)
509+
}
510+
}
511+
512+
impl TryFrom<&Structure<'_>> for crate::model::NfcState {
513+
type Error = zvariant::Error;
514+
515+
fn try_from(structure: &Structure<'_>) -> Result<Self, Self::Error> {
516+
let (tag, value) = parse_tag_value_struct(structure)?;
517+
match tag {
518+
0x01 => Ok(Self::Idle),
519+
0x02 => Ok(Self::Waiting),
520+
0x04 => Ok(Self::Connected),
521+
0x05 => {
522+
let attempts_left: i32 = value.downcast_ref()?;
523+
let attempts_left = if attempts_left == -1 {
524+
None
525+
} else {
526+
Some(attempts_left as u32)
527+
};
528+
Ok(Self::NeedsPin { attempts_left })
529+
}
530+
0x06 => {
531+
let attempts_left: i32 = value.downcast_ref()?;
532+
let attempts_left = if attempts_left == -1 {
533+
None
534+
} else {
535+
Some(attempts_left as u32)
536+
};
537+
Ok(Self::NeedsUserVerification { attempts_left })
538+
}
539+
0x08 => {
540+
let creds: Array = value.downcast_ref()?;
541+
let creds: Result<Vec<crate::model::Credential>, zvariant::Error> = creds
542+
.iter()
543+
.map(|v| v.try_to_owned().unwrap())
544+
.map(|v| {
545+
let cred: Result<crate::model::Credential, zvariant::Error> =
546+
Value::from(v)
547+
.downcast::<Credential>()
548+
.map(crate::model::Credential::from);
549+
cred
550+
})
551+
.collect();
552+
Ok(Self::SelectCredential { creds: creds? })
553+
}
554+
0x09 => Ok(Self::Completed),
555+
0x0A => {
556+
let err_code: &str = value.downcast_ref()?;
557+
let err = match err_code {
558+
"AuthenticatorError" => crate::model::Error::AuthenticatorError,
559+
"NoCredentials" => crate::model::Error::NoCredentials,
560+
"CredentialExcluded" => crate::model::Error::CredentialExcluded,
561+
"PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted,
562+
s => crate::model::Error::Internal(String::from(s)),
563+
};
564+
Ok(Self::Failed(err))
565+
}
566+
_ => Err(zvariant::Error::IncorrectType),
567+
}
568+
}
569+
}
570+
571+
impl TryFrom<Structure<'_>> for crate::model::NfcState {
572+
type Error = zvariant::Error;
573+
574+
fn try_from(structure: Structure<'_>) -> Result<Self, Self::Error> {
575+
Self::try_from(&structure)
576+
}
577+
}
578+
579+
impl Serialize for crate::model::NfcState {
580+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
581+
where
582+
S: serde::Serializer,
583+
{
584+
let structure: Structure = self.into();
585+
structure.serialize(serializer)
586+
}
587+
}
588+
589+
impl<'de> Deserialize<'de> for crate::model::NfcState {
590+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
591+
where
592+
D: serde::Deserializer<'de>,
593+
{
594+
deserialize_tag_value(deserializer)
595+
}
596+
}
597+
465598
fn deserialize_tag_value<'a, 'de, T, D>(deserializer: D) -> Result<T, D::Error>
466599
where
467600
T: TryFrom<Structure<'a>>,

0 commit comments

Comments
 (0)