Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
180 changes: 150 additions & 30 deletions credentialsd-common/Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion credentialsd-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license = "LGPL-3.0-only"

[dependencies]
futures-lite = "2.6.0"
libwebauthn = "0.2"
# libwebauthn = "0.2"
libwebauthn = { git = "https://github.com/msirringhaus/libwebauthn.git", branch="nfc_followup", features = ["libnfc", "pcsc"] }
serde = { version = "1", features = ["derive"] }
zvariant = "5.6.0"
1 change: 1 addition & 0 deletions credentialsd-common/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub trait FlowController {

fn get_hybrid_credential(&mut self) -> impl Future<Output = Result<(), ()>> + Send;
fn get_usb_credential(&mut self) -> impl Future<Output = Result<(), ()>> + Send;
fn get_nfc_credential(&mut self) -> impl Future<Output = Result<(), ()>> + Send;
fn subscribe(
&mut self,
) -> impl Future<
Expand Down
41 changes: 40 additions & 1 deletion credentialsd-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ impl Transport {
}
}

#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ViewUpdate {
SetTitle(String),
SetDevices(Vec<Device>),
Expand All @@ -183,6 +183,9 @@ pub enum ViewUpdate {
UsbNeedsUserVerification { attempts_left: Option<u32> },
UsbNeedsUserPresence,

NfcNeedsPin { attempts_left: Option<u32> },
NfcNeedsUserVerification { attempts_left: Option<u32> },

HybridNeedsQrCode(String),
HybridConnecting,
HybridConnected,
Expand Down Expand Up @@ -262,10 +265,46 @@ pub enum UsbState {
Failed(Error),
}

/// Used to share public state between credential service and UI.
#[derive(Clone, Debug, Default)]
pub enum NfcState {
/// Not polling for FIDO USB device.
#[default]
Idle,

/// Awaiting FIDO USB device to be plugged in.
Waiting,

/// USB device connected, prompt user to tap
Connected,

/// The device needs the PIN to be entered.
NeedsPin { attempts_left: Option<u32> },

/// The device needs on-device user verification.
NeedsUserVerification { attempts_left: Option<u32> },

// TODO: implement cancellation
// This isn't actually sent from the server.
//UserCancelled,
/// Multiple credentials have been found and the user has to select which to use
SelectCredential {
/// List of user-identities to decide which to use.
creds: Vec<Credential>,
},

/// USB tapped, received credential
Completed,

/// Interaction with the authenticator failed.
Failed(Error),
}

#[derive(Clone, Debug)]
pub enum BackgroundEvent {
UsbStateChanged(UsbState),
HybridQrStateChanged(HybridState),
NfcStateChanged(NfcState),
}

#[derive(Debug, Clone)]
Expand Down
133 changes: 133 additions & 0 deletions credentialsd-common/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ impl From<&BackgroundEvent> for Structure<'_> {
BackgroundEvent::HybridQrStateChanged(state) => {
tag_value_to_struct(0x02, Some(Value::Structure(state.into())))
}
BackgroundEvent::NfcStateChanged(state) => {
tag_value_to_struct(0x03, Some(Value::Structure(state.into())))
}
}
}
}
Expand All @@ -49,6 +52,10 @@ impl TryFrom<&Structure<'_>> for BackgroundEvent {
(&structure).try_into()?,
))
}
0x03 => {
let structure: Structure = value.downcast_ref()?;
Ok(BackgroundEvent::NfcStateChanged((&structure).try_into()?))
}
_ => Err(zvariant::Error::Message(format!(
"Unknown BackgroundEvent tag : {tag}"
))),
Expand Down Expand Up @@ -336,6 +343,10 @@ impl Type for crate::model::UsbState {
const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE;
}

impl Type for crate::model::NfcState {
const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE;
}

impl From<&crate::model::UsbState> for Structure<'_> {
fn from(value: &crate::model::UsbState) -> Self {
let (tag, value): (u8, Option<Value>) = match value {
Expand Down Expand Up @@ -462,6 +473,128 @@ impl<'de> Deserialize<'de> for crate::model::UsbState {
}
}

impl From<&crate::model::NfcState> for Structure<'_> {
fn from(value: &crate::model::NfcState) -> Self {
let (tag, value): (u8, Option<Value>) = match value {
crate::model::NfcState::Idle => (0x01, None),
crate::model::NfcState::Waiting => (0x02, None),
crate::model::NfcState::Connected => (0x04, None),
// TODO: Add pin request reason to this struct
crate::model::NfcState::NeedsPin { attempts_left } => {
let num = match attempts_left {
Some(num) => *num as i32,
None => -1,
};
(0x05, Some(Value::I32(num)))
}
crate::model::NfcState::NeedsUserVerification { attempts_left } => {
let num = match attempts_left {
Some(num) => *num as i32,
None => -1,
};
(0x06, Some(Value::I32(num)))
}
crate::model::NfcState::SelectCredential { creds } => {
let creds: Vec<Credential> = creds.iter().map(Credential::from).collect();
let value = Value::new(creds);
(0x08, Some(value))
}
crate::model::NfcState::Completed => (0x09, None),
crate::model::NfcState::Failed(error) => {
let value = Value::<'_>::from(error.to_string());
(0x0A, Some(value))
}
};
tag_value_to_struct(tag, value)
}
}

impl TryFrom<&Structure<'_>> for crate::model::NfcState {
type Error = zvariant::Error;

fn try_from(structure: &Structure<'_>) -> Result<Self, Self::Error> {
let (tag, value) = parse_tag_value_struct(structure)?;
match tag {
0x01 => Ok(Self::Idle),
0x02 => Ok(Self::Waiting),
0x04 => Ok(Self::Connected),
0x05 => {
let attempts_left: i32 = value.downcast_ref()?;
let attempts_left = if attempts_left == -1 {
None
} else {
Some(attempts_left as u32)
};
Ok(Self::NeedsPin { attempts_left })
}
0x06 => {
let attempts_left: i32 = value.downcast_ref()?;
let attempts_left = if attempts_left == -1 {
None
} else {
Some(attempts_left as u32)
};
Ok(Self::NeedsUserVerification { attempts_left })
}
0x08 => {
let creds: Array = value.downcast_ref()?;
let creds: Result<Vec<crate::model::Credential>, zvariant::Error> = creds
.iter()
.map(|v| v.try_to_owned().unwrap())
.map(|v| {
let cred: Result<crate::model::Credential, zvariant::Error> =
Value::from(v)
.downcast::<Credential>()
.map(crate::model::Credential::from);
cred
})
.collect();
Ok(Self::SelectCredential { creds: creds? })
}
0x09 => Ok(Self::Completed),
0x0A => {
let err_code: &str = value.downcast_ref()?;
let err = match err_code {
"AuthenticatorError" => crate::model::Error::AuthenticatorError,
"NoCredentials" => crate::model::Error::NoCredentials,
"CredentialExcluded" => crate::model::Error::CredentialExcluded,
"PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted,
s => crate::model::Error::Internal(String::from(s)),
};
Ok(Self::Failed(err))
}
_ => Err(zvariant::Error::IncorrectType),
}
}
}

impl TryFrom<Structure<'_>> for crate::model::NfcState {
type Error = zvariant::Error;

fn try_from(structure: Structure<'_>) -> Result<Self, Self::Error> {
Self::try_from(&structure)
}
}

impl Serialize for crate::model::NfcState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let structure: Structure = self.into();
structure.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for crate::model::NfcState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_tag_value(deserializer)
}
}

fn deserialize_tag_value<'a, 'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: TryFrom<Structure<'a>>,
Expand Down
Loading
Loading