libwebauthn 0.5.0

FIDO2 (WebAuthn) and FIDO U2F platform library for Linux written in Rust
Documentation
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
use std::time::Duration;

use crate::fido::FidoRevision;
use crate::proto::ctap1::apdu::{ApduRequest, ApduResponse};
use crate::proto::ctap2::cbor::{CborRequest, CborResponse};
use crate::proto::CtapError;
use crate::transport::ble::btleplug;
use crate::transport::channel::{AuthTokenData, Channel, ChannelStatus, Ctap2AuthTokenStore};
use crate::transport::device::SupportedProtocols;
use crate::transport::error::TransportError;
use crate::webauthn::error::Error;
use crate::UvUpdate;

use super::btleplug::manager::SupportedRevisions;
use super::btleplug::Connection;
use super::framing::{BleCommand, BleFrame};
use super::BleDevice;

use async_trait::async_trait;
use tokio::sync::broadcast;
use tracing::{debug, instrument, trace, Level};

#[derive(Debug)]
pub struct BleChannel<'a> {
    status: ChannelStatus,
    device: &'a BleDevice,
    connection: Connection,
    revision: FidoRevision,
    auth_token_data: Option<AuthTokenData>,
    ux_update_sender: broadcast::Sender<UvUpdate>,
}

impl<'a> BleChannel<'a> {
    pub async fn new(
        device: &'a BleDevice,
        revisions: &SupportedRevisions,
    ) -> Result<BleChannel<'a>, Error> {
        let (ux_update_sender, _) = broadcast::channel(16);

        let revision = revisions
            .select_best()
            .ok_or(Error::Transport(TransportError::NegotiationFailed))?;
        let connection = btleplug::connect(&device.btleplug_device.peripheral, &revision)
            .await
            .or(Err(Error::Transport(TransportError::ConnectionFailed)))?;
        let channel = BleChannel {
            status: ChannelStatus::Ready,
            device,
            connection,
            revision,
            auth_token_data: None,
            ux_update_sender,
        };
        channel
            .connection
            .subscribe()
            .await
            .or(Err(Error::Transport(TransportError::TransportUnavailable)))?;
        Ok(channel)
    }
}

impl Display for BleChannel<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        self.device.fmt(f)
    }
}

#[async_trait]
impl<'a> Channel for BleChannel<'a> {
    type UxUpdate = UvUpdate;

    async fn supported_protocols(&self) -> Result<SupportedProtocols, Error> {
        Ok(self.revision.into())
    }

    async fn status(&self) -> ChannelStatus {
        self.status
    }

    async fn close(&mut self) {
        // BLE disconnection is handled when the Connection is dropped
        trace!("BLE channel close() called — no-op, cleanup on drop");
    }

    #[instrument(level = Level::DEBUG, skip_all)]
    async fn apdu_send(&mut self, request: &ApduRequest, _timeout: Duration) -> Result<(), Error> {
        debug!({rev = ?self.revision}, "Sending APDU request");
        trace!(?request);

        let request_apdu_packet = request.raw_long().or(Err(TransportError::InvalidFraming))?;
        let request_frame = BleFrame::new(BleCommand::Msg, &request_apdu_packet);
        self.connection
            .frame_send(&request_frame)
            .await
            .or(Err(Error::Transport(TransportError::ConnectionFailed)))?;
        Ok(())
    }

    #[instrument(level = Level::DEBUG, skip_all)]
    async fn apdu_recv(&mut self, timeout: Duration) -> Result<ApduResponse, Error> {
        let response_frame = self
            .connection
            .frame_recv(timeout)
            .await
            .map_err(|e| match e {
                btleplug::Error::Timeout => Error::Transport(TransportError::Timeout),
                _ => Error::Transport(TransportError::ConnectionFailed),
            })?;

        match response_frame.cmd {
            BleCommand::Error => return Err(Error::Transport(TransportError::InvalidFraming)), // Encapsulation layer error
            BleCommand::Cancel => return Err(Error::Ctap(CtapError::KeepAliveCancel)),
            BleCommand::Keepalive | BleCommand::Ping => return Err(Error::Ctap(CtapError::Other)), // Unexpected
            BleCommand::Msg => {}
        }
        let response_apdu_packet = &response_frame.data;
        let response_apdu: ApduResponse = response_apdu_packet
            .try_into()
            .or(Err(TransportError::InvalidFraming))?;

        debug!("Received APDU response");
        trace!(?response_apdu);
        Ok(response_apdu)
    }

    #[instrument(level = Level::DEBUG, skip_all)]
    async fn cbor_send(
        &mut self,
        request: &CborRequest,
        _timeout: std::time::Duration,
    ) -> Result<(), Error> {
        debug!("Sending CBOR request");
        trace!(?request);

        let cbor_request = request
            .raw_long()
            .map_err(|e| TransportError::IoError(e.kind()))?;
        let request_frame = BleFrame::new(BleCommand::Msg, &cbor_request);
        self.connection
            .frame_send(&request_frame)
            .await
            .or(Err(Error::Transport(TransportError::ConnectionFailed)))?;
        Ok(())
    }

    #[instrument(level = Level::DEBUG, skip_all)]
    async fn cbor_recv(&mut self, timeout: std::time::Duration) -> Result<CborResponse, Error> {
        let response_frame = self
            .connection
            .frame_recv(timeout)
            .await
            .map_err(|e| match e {
                btleplug::Error::Timeout => Error::Transport(TransportError::Timeout),
                _ => Error::Transport(TransportError::ConnectionFailed),
            })?;
        match response_frame.cmd {
            BleCommand::Error => return Err(Error::Transport(TransportError::InvalidFraming)), // Encapsulation layer error
            BleCommand::Cancel => return Err(Error::Ctap(CtapError::KeepAliveCancel)),
            BleCommand::Keepalive | BleCommand::Ping => return Err(Error::Ctap(CtapError::Other)), // Unexpected
            BleCommand::Msg => {}
        }
        let cbor_response_packet = &response_frame.data;
        let cbor_response: CborResponse = cbor_response_packet
            .try_into()
            .or(Err(TransportError::InvalidFraming))?;

        debug!("Received CBOR response");
        trace!(?cbor_response);
        Ok(cbor_response)
    }

    fn get_ux_update_sender(&self) -> &broadcast::Sender<Self::UxUpdate> {
        &self.ux_update_sender
    }
}

impl Ctap2AuthTokenStore for BleChannel<'_> {
    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;
    }
}