libwebauthn 0.5.1

FIDO2 (WebAuthn) and FIDO U2F platform library for Linux written in Rust
Documentation
use async_trait::async_trait;
use std::fmt;
#[allow(unused_imports)]
use tracing::{debug, info, instrument, trace};

use crate::{
    transport::{device::Device, Channel},
    webauthn::Error,
};

use super::channel::NfcChannel;
#[cfg(feature = "nfc-backend-libnfc")]
use super::libnfc;
#[cfg(feature = "nfc-backend-pcsc")]
use super::pcsc;
use super::{Context, Nfc};

#[derive(Clone, Debug)]
enum DeviceInfo {
    #[cfg(feature = "nfc-backend-libnfc")]
    LibNfc(libnfc::Info),
    #[cfg(feature = "nfc-backend-pcsc")]
    Pcsc(pcsc::Info),
}

#[derive(Clone, Debug)]
pub struct NfcDevice {
    info: DeviceInfo,
}

impl fmt::Display for DeviceInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            #[cfg(feature = "nfc-backend-libnfc")]
            DeviceInfo::LibNfc(info) => write!(f, "{}", info),
            #[cfg(feature = "nfc-backend-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 = "nfc-backend-libnfc")]
    pub fn new_libnfc(info: libnfc::Info) -> Self {
        NfcDevice {
            info: DeviceInfo::LibNfc(info),
        }
    }

    #[cfg(feature = "nfc-backend-pcsc")]
    pub fn new_pcsc(info: pcsc::Info) -> Self {
        NfcDevice {
            info: DeviceInfo::Pcsc(info),
        }
    }

    async fn channel_sync(&self) -> Result<NfcChannel<Context>, Error> {
        trace!("nfc channel {:?}", self);
        let mut channel: NfcChannel<Context> = match &self.info {
            #[cfg(feature = "nfc-backend-libnfc")]
            DeviceInfo::LibNfc(info) => info.channel(),
            #[cfg(feature = "nfc-backend-pcsc")]
            DeviceInfo::Pcsc(info) => info.channel(),
        }?;

        channel.select_fido2().await?;
        Ok(channel)
    }
}

#[async_trait]
impl<'d> Device<'d, Nfc, NfcChannel<Context>> for NfcDevice {
    async fn channel(&'d mut self) -> Result<NfcChannel<Context>, Error> {
        self.channel_sync().await
    }
}

async fn is_fido(device: &NfcDevice) -> bool {
    async fn inner(device: &NfcDevice) -> Result<bool, Error> {
        let chan = device.channel_sync().await?;
        let protocols = chan.supported_protocols().await?;
        Ok(protocols.fido2 || protocols.u2f)
    }

    inner(device).await.is_ok()
}

#[instrument]
/// Returns Ok(None) if no devices are found, otherwise returns
/// the first device found by either NFC-backend.
pub async fn get_nfc_device() -> Result<Option<NfcDevice>, Error> {
    // See https://github.com/linux-credentials/libwebauthn/issues/154 for
    // why we only return the first found device here.
    // We'd otherwise need to deduplicate found devices here, as
    // we'll potentially have the same device discovered by
    // both backends and thus added multiple times to the list.
    let list_devices_fns = [
        #[cfg(feature = "nfc-backend-libnfc")]
        libnfc::list_devices,
        #[cfg(feature = "nfc-backend-pcsc")]
        pcsc::list_devices,
    ];

    for list_devices in list_devices_fns {
        for device in list_devices()? {
            if is_fido(&device).await {
                return Ok(Some(device));
            }
        }
    }

    Ok(None)
}

#[instrument]
pub fn is_nfc_available() -> bool {
    let mut available = false;
    #[cfg(feature = "nfc-backend-libnfc")]
    {
        available |= libnfc::is_nfc_available();
    }
    #[cfg(feature = "nfc-backend-pcsc")]
    {
        available |= pcsc::is_nfc_available();
    }

    available
}