use crate::assertion::{AssertRequest, Assertions};
use crate::cbor::CBORInfo;
use crate::credentials::Credential;
use crate::credman::CredentialManagement;
use crate::error::{Error, Result};
use crate::utils::check;
use bitflags::bitflags;
use ffi::fido_dev_t;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::ptr::NonNull;
use zeroize::Zeroizing;
pub struct DeviceList<'a> {
ptr: NonNull<ffi::fido_dev_info_t>,
idx: usize,
found: usize,
_p: PhantomData<&'a ()>,
}
impl<'a> DeviceList<'a> {
pub fn list_devices(max: usize) -> DeviceList<'a> {
unsafe {
let mut found = 0;
let ptr = ffi::fido_dev_info_new(max);
ffi::fido_dev_info_manifest(ptr, max, &mut found);
DeviceList {
ptr: NonNull::new_unchecked(ptr),
idx: 0,
found,
_p: PhantomData,
}
}
}
}
impl<'a> Iterator for DeviceList<'a> {
type Item = DeviceInfo<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.found {
return None;
}
unsafe {
let ptr = self.ptr.as_ptr();
let info = ffi::fido_dev_info_ptr(ptr, self.idx);
let path = ffi::fido_dev_info_path(info);
let path = CStr::from_ptr(path);
let product_id = ffi::fido_dev_info_product(info);
let vendor_id = ffi::fido_dev_info_vendor(info);
let manufacturer = ffi::fido_dev_info_manufacturer_string(info);
let manufacturer = CStr::from_ptr(manufacturer);
let product = ffi::fido_dev_info_product_string(info);
let product = CStr::from_ptr(product);
self.idx += 1;
Some(DeviceInfo {
path,
product_id,
vendor_id,
manufacturer,
product,
})
}
}
}
impl<'a> ExactSizeIterator for DeviceList<'a> {
fn len(&self) -> usize {
self.found
}
}
impl<'a> Drop for DeviceList<'a> {
fn drop(&mut self) {
unsafe {
ffi::fido_dev_info_free(&mut self.ptr.as_ptr(), self.found);
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DeviceInfo<'a> {
pub path: &'a CStr,
pub product_id: i16,
pub vendor_id: i16,
pub manufacturer: &'a CStr,
pub product: &'a CStr,
}
impl<'a> DeviceInfo<'a> {
pub fn open(&self) -> Result<Device> {
unsafe {
let ptr = ffi::fido_dev_new();
check(ffi::fido_dev_open(ptr, self.path.as_ptr()))?;
let ptr = NonNull::new_unchecked(ptr);
Ok(Device { ptr })
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct DeviceCancel(NonNull<fido_dev_t>);
impl DeviceCancel {
pub fn cancel(&self) {
unsafe {
ffi::fido_dev_cancel(self.0.as_ptr());
}
}
}
pub struct Device {
pub(crate) ptr: NonNull<fido_dev_t>,
}
impl Device {
pub fn open(path: impl AsRef<str>) -> Result<Device> {
let path = CString::new(path.as_ref())?;
unsafe {
let dev = ffi::fido_dev_new();
assert!(!dev.is_null());
check(ffi::fido_dev_open(dev, path.as_ptr()))?;
Ok(Device {
ptr: NonNull::new_unchecked(dev),
})
}
}
pub fn cancel_handle(&self) -> DeviceCancel {
DeviceCancel(self.ptr)
}
pub fn force_u2f(&self) {
unsafe {
ffi::fido_dev_force_u2f(self.ptr.as_ptr());
}
}
pub fn force_fido2(&self) {
unsafe {
ffi::fido_dev_force_fido2(self.ptr.as_ptr());
}
}
pub fn is_fido2(&self) -> bool {
unsafe { ffi::fido_dev_is_fido2(self.ptr.as_ptr()) }
}
pub fn is_winhello(&self) -> bool {
unsafe { ffi::fido_dev_is_winhello(self.ptr.as_ptr()) }
}
pub fn supports_credman(&self) -> bool {
unsafe { ffi::fido_dev_supports_credman(self.ptr.as_ptr()) }
}
pub fn supports_cred_prot(&self) -> bool {
unsafe { ffi::fido_dev_supports_cred_prot(self.ptr.as_ptr()) }
}
pub fn supports_permission(&self) -> bool {
unsafe { ffi::fido_dev_supports_permissions(self.ptr.as_ptr()) }
}
pub fn supports_pin(&self) -> bool {
unsafe { ffi::fido_dev_supports_pin(self.ptr.as_ptr()) }
}
pub fn supports_uv(&self) -> bool {
unsafe { ffi::fido_dev_supports_uv(self.ptr.as_ptr()) }
}
pub fn has_pin(&self) -> bool {
unsafe { ffi::fido_dev_has_pin(self.ptr.as_ptr()) }
}
pub fn has_uv(&self) -> bool {
unsafe { ffi::fido_dev_has_uv(self.ptr.as_ptr()) }
}
pub fn ctap_protocol(&self) -> CTAPHIDInfo {
unsafe {
let protocol = ffi::fido_dev_protocol(self.ptr.as_ptr());
let build = ffi::fido_dev_build(self.ptr.as_ptr());
let flags = ffi::fido_dev_flags(self.ptr.as_ptr());
let flags = CTAPHIDFlags::from_bits_truncate(flags);
let major = ffi::fido_dev_major(self.ptr.as_ptr());
let minor = ffi::fido_dev_minor(self.ptr.as_ptr());
CTAPHIDInfo {
protocol,
build,
flags,
major,
minor,
}
}
}
pub fn info(&self) -> Result<CBORInfo> {
let info = CBORInfo::new();
unsafe {
check(ffi::fido_dev_get_cbor_info(
self.ptr.as_ptr(),
info.ptr.as_ptr(),
))?;
}
Ok(info)
}
pub fn get_retry_count(&self) -> Result<i32> {
let mut res = 0;
unsafe {
check(ffi::fido_dev_get_retry_count(
self.ptr.as_ptr(),
&mut res as *mut i32,
))?;
}
Ok(res)
}
pub fn get_uv_retry_count(&self) -> Result<i32> {
let mut res = 0;
unsafe {
check(ffi::fido_dev_get_uv_retry_count(
self.ptr.as_ptr(),
&mut res as *mut i32,
))?;
}
Ok(res)
}
pub fn make_credential(&self, credential: &mut Credential, pin: Option<&str>) -> Result<()> {
let pin = pin.map(CString::new).transpose()?;
let pin_ptr = match &pin {
Some(pin) => pin.as_ptr(),
None => std::ptr::null(),
};
unsafe {
check(ffi::fido_dev_make_cred(
self.ptr.as_ptr(),
credential.0.as_ptr(),
pin_ptr,
))?;
}
Ok(())
}
pub fn get_assertion(&self, request: AssertRequest, pin: Option<&str>) -> Result<Assertions> {
let pin = pin.map(CString::new).transpose()?;
let pin_ptr = match &pin {
Some(pin) => pin.as_ptr(),
None => std::ptr::null(),
};
unsafe {
check(ffi::fido_dev_get_assert(
self.ptr.as_ptr(),
request.0.ptr.as_ptr(),
pin_ptr,
))?;
}
Ok(request.0)
}
pub fn credman(&self, pin: &str) -> Result<CredentialManagement<'_>> {
if !self.supports_credman() {
return Err(Error::Unsupported);
}
let ptr = unsafe { ffi::fido_credman_metadata_new() };
let pin = CString::new(pin)?;
let pin_ptr = pin.as_ptr();
unsafe {
check(ffi::fido_credman_get_dev_metadata(
self.ptr.as_ptr(),
ptr,
pin_ptr,
))?;
}
let ptr = unsafe { NonNull::new_unchecked(ptr) };
let credman = CredentialManagement::new(ptr, &self, Zeroizing::new(pin));
Ok(credman)
}
}
impl Drop for Device {
fn drop(&mut self) {
unsafe {
let _ = ffi::fido_dev_close(self.ptr.as_ptr());
ffi::fido_dev_free(&mut self.ptr.as_ptr());
}
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CTAPHIDFlags: u8 {
const WINK = ffi::FIDO_CAP_WINK as u8;
const CBOR = ffi::FIDO_CAP_CBOR as u8;
const NMSG = ffi::FIDO_CAP_NMSG as u8;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CTAPHIDInfo {
pub protocol: u8,
pub build: u8,
pub flags: CTAPHIDFlags,
pub major: u8,
pub minor: u8,
}