challenge_response 0.5.46

Perform HMAC-SHA1 and OTP challenges with YubiKey, OnlyKey and NitroKey, in pure Rust.
Documentation
use std::time::Duration;

use nusb::{Device as NUSBDevice, Interface, MaybeFuture};

use crate::error::ChallengeResponseError;
use crate::usb::{Backend, Device, HID_GET_REPORT, HID_SET_REPORT, PRODUCT_ID, REPORT_TYPE_FEATURE, VENDOR_ID};

pub struct NUSBBackend {}

impl Backend<NUSBDevice, Interface> for NUSBBackend {
    fn new() -> Result<Self, ChallengeResponseError> {
        Ok(Self {})
    }

    fn open_device(
        &mut self,
        bus_id: u8,
        address_id: u8,
    ) -> Result<(NUSBDevice, Vec<Interface>), ChallengeResponseError> {
        let nusb_devices = match nusb::list_devices().wait() {
            Ok(d) => d,
            Err(e) => return Err(e.into()),
        };
        for device_info in nusb_devices {
            if device_info.busnum() != bus_id || device_info.device_address() != address_id {
                continue;
            }

            let device = match device_info.open().wait() {
                Ok(d) => d,
                Err(_) => {
                    return Err(ChallengeResponseError::OpenDeviceError);
                }
            };

            let mut interfaces: Vec<Interface> = Vec::new();
            for interface in device_info.interfaces() {
                let interface = match device
                    .detach_and_claim_interface(interface.interface_number())
                    .wait()
                {
                    Ok(interface) => interface,
                    Err(_) => continue,
                };

                interfaces.push(interface);
            }
            return Ok((device, interfaces));
        }

        Err(ChallengeResponseError::DeviceNotFound)
    }

    fn close_device(
        &self,
        mut _handle: NUSBDevice,
        _interfaces: Vec<Interface>,
    ) -> Result<(), ChallengeResponseError> {
        Ok(())
    }

    fn read(&self, handle: &mut NUSBDevice, buf: &mut [u8]) -> Result<usize, ChallengeResponseError> {
        assert_eq!(buf.len(), 8);

        let control_type = nusb::transfer::ControlType::Class;
        let control_in = nusb::transfer::ControlIn {
            control_type,
            recipient: nusb::transfer::Recipient::Interface,
            request: HID_GET_REPORT,
            value: REPORT_TYPE_FEATURE << 8,
            index: 0,
            length: 8,
        };

        match handle.control_in(control_in, Duration::new(2, 0)).wait() {
            Ok(r) => {
                buf.copy_from_slice(&r);
                Ok(r.len())
            }
            Err(_e) => Err(ChallengeResponseError::CanNotReadFromDevice),
        }
    }

    fn raw_write(&self, handle: &mut NUSBDevice, packet: &[u8]) -> Result<(), ChallengeResponseError> {
        let control_type = nusb::transfer::ControlType::Class;
        let control_out = nusb::transfer::ControlOut {
            control_type,
            recipient: nusb::transfer::Recipient::Interface,
            request: HID_SET_REPORT,
            value: REPORT_TYPE_FEATURE << 8,
            index: 0,
            data: packet,
        };

        match handle.control_out(control_out, Duration::new(2, 0)).wait() {
            Ok(_) => Ok(()),
            Err(_) => Err(ChallengeResponseError::CanNotWriteToDevice),
        }
    }

    fn find_device(&mut self) -> Result<Device, ChallengeResponseError> {
        match self.find_all_devices() {
            Ok(devices) => {
                if !devices.is_empty() {
                    Ok(devices[0].clone())
                } else {
                    Err(ChallengeResponseError::DeviceNotFound)
                }
            }
            Err(e) => Err(e),
        }
    }

    fn find_device_from_serial(&mut self, serial: u32) -> Result<Device, ChallengeResponseError> {
        let nusb_devices = nusb::list_devices().wait()?;
        for device_info in nusb_devices {
            let product_id = device_info.product_id();
            let vendor_id = device_info.vendor_id();

            if !VENDOR_ID.contains(&vendor_id) || !PRODUCT_ID.contains(&product_id) {
                continue;
            }

            let device_serial =
                match self.read_serial_from_device(device_info.busnum(), device_info.device_address()) {
                    Ok(s) => s,
                    Err(_) => continue,
                };

            if device_serial == serial {
                return Ok(Device {
                    name: match device_info.manufacturer_string() {
                        Some(name) => Some(name.to_string()),
                        None => Some("unknown".to_string()),
                    },
                    serial: Some(serial),
                    product_id,
                    vendor_id,
                    bus_id: device_info.busnum(),
                    address_id: device_info.device_address(),
                });
            }
        }
        Err(ChallengeResponseError::DeviceNotFound)
    }

    fn find_all_devices(&mut self) -> Result<Vec<Device>, ChallengeResponseError> {
        let mut devices: Vec<Device> = Vec::new();
        let nusb_devices = nusb::list_devices().wait()?;
        for device_info in nusb_devices {
            let product_id = device_info.product_id();
            let vendor_id = device_info.vendor_id();

            if !VENDOR_ID.contains(&vendor_id) || !PRODUCT_ID.contains(&product_id) {
                continue;
            }

            let device_serial = self
                .read_serial_from_device(device_info.busnum(), device_info.device_address())
                .ok();

            devices.push(Device {
                name: match device_info.manufacturer_string() {
                    Some(name) => Some(name.to_string()),
                    None => Some("unknown".to_string()),
                },
                serial: device_serial,
                product_id,
                vendor_id,
                bus_id: device_info.busnum(),
                address_id: device_info.device_address(),
            });
        }
        Ok(devices)
    }
}