зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1468349 - Web Authentication - Add FreeBSD Support
Upstream PR: https://github.com/jcjones/u2f-hid-rs/pull/62 * Extract hidproto module from linux::hidraw Make the protocol parts independent of Linux code, in preparation for adding FreeBSD support. * Add FreeBSD (uhid + devd) support Tested with a YubiKey 4. Differential Revision: https://phabricator.services.mozilla.com/D1636
This commit is contained in:
Родитель
b0a1468c01
Коммит
ee92007fde
|
@ -1,11 +1,14 @@
|
|||
[package]
|
||||
name = "u2fhid"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Kyle Machulis <kyle@nonpolynomial.com>", "J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>"]
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libudev = "^0.2"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dependencies]
|
||||
devd-rs = "0.2.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation-sys = "0.6.0"
|
||||
core-foundation = "0.6.0"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
This is a cross-platform library for interacting with U2F Security Key-type devices via Rust.
|
||||
|
||||
* **Supported Platforms**: Windows, Linux, and Mac OS X.
|
||||
* **Supported Platforms**: Windows, Linux, FreeBSD, and macOS.
|
||||
* **Supported HID Transports**: USB.
|
||||
* **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html).
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use std::ffi::{CString, OsString};
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
use consts::CID_BROADCAST;
|
||||
use platform::uhid;
|
||||
use u2ftypes::U2FDevice;
|
||||
use util::from_unix_result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
path: OsString,
|
||||
fd: libc::c_int,
|
||||
cid: [u8; 4],
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(path: OsString) -> io::Result<Self> {
|
||||
let cstr = CString::new(path.as_bytes())?;
|
||||
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
|
||||
let fd = from_unix_result(fd)?;
|
||||
Ok(Self {
|
||||
path,
|
||||
fd,
|
||||
cid: CID_BROADCAST,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_u2f(&self) -> bool {
|
||||
uhid::is_u2f_device(self.fd)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Device {
|
||||
fn drop(&mut self) {
|
||||
// Close the fd, ignore any errors.
|
||||
let _ = unsafe { libc::close(self.fd) };
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Device {
|
||||
fn eq(&self, other: &Device) -> bool {
|
||||
self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Device {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
|
||||
let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
|
||||
from_unix_result(rv as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Device {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let report_id = buf[0] as i64;
|
||||
// Skip report number when not using numbered reports.
|
||||
let start = if report_id == 0x0 { 1 } else { 0 };
|
||||
let data = &buf[start..];
|
||||
|
||||
let data_ptr = data.as_ptr() as *const libc::c_void;
|
||||
let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) };
|
||||
from_unix_result(rv as usize + 1)
|
||||
}
|
||||
|
||||
// USB HID writes don't buffer, so this will be a nop.
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
||||
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||
self.cid = cid;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod device;
|
||||
pub mod transaction;
|
||||
|
||||
mod monitor;
|
||||
mod uhid;
|
|
@ -0,0 +1,139 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use devd_rs;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, io};
|
||||
|
||||
use runloop::RunLoop;
|
||||
|
||||
const POLL_TIMEOUT: usize = 100;
|
||||
|
||||
pub enum Event {
|
||||
Add(OsString),
|
||||
Remove(OsString),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn from_devd(event: devd_rs::Event) -> Option<Self> {
|
||||
match event {
|
||||
devd_rs::Event::Attach {
|
||||
ref dev,
|
||||
parent: _,
|
||||
location: _,
|
||||
} if dev.starts_with("uhid") =>
|
||||
{
|
||||
Some(Event::Add(("/dev/".to_owned() + dev).into()))
|
||||
}
|
||||
devd_rs::Event::Detach {
|
||||
ref dev,
|
||||
parent: _,
|
||||
location: _,
|
||||
} if dev.starts_with("uhid") =>
|
||||
{
|
||||
Some(Event::Remove(("/dev/".to_owned() + dev).into()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_error(e: devd_rs::Error) -> io::Error {
|
||||
e.into()
|
||||
}
|
||||
|
||||
pub struct Monitor<F>
|
||||
where
|
||||
F: Fn(OsString, &Fn() -> bool) + Sync,
|
||||
{
|
||||
runloops: HashMap<OsString, RunLoop>,
|
||||
new_device_cb: Arc<F>,
|
||||
}
|
||||
|
||||
impl<F> Monitor<F>
|
||||
where
|
||||
F: Fn(OsString, &Fn() -> bool) + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(new_device_cb: F) -> Self {
|
||||
Self {
|
||||
runloops: HashMap::new(),
|
||||
new_device_cb: Arc::new(new_device_cb),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, alive: &Fn() -> bool) -> io::Result<()> {
|
||||
let mut ctx = devd_rs::Context::new().map_err(convert_error)?;
|
||||
|
||||
// Iterate all existing devices.
|
||||
for dev in fs::read_dir("/dev")? {
|
||||
if let Ok(dev) = dev {
|
||||
let filename_ = dev.file_name();
|
||||
let filename = filename_.to_str().unwrap_or("");
|
||||
if filename.starts_with("uhid") {
|
||||
self.add_device(("/dev/".to_owned() + filename).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop until we're stopped by the controlling thread, or fail.
|
||||
while alive() {
|
||||
// Wait for new events, break on failure.
|
||||
match ctx.wait_for_event(POLL_TIMEOUT) {
|
||||
Err(devd_rs::Error::Timeout) => (),
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(event) => {
|
||||
if let Some(event) = Event::from_devd(event) {
|
||||
self.process_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all tracked devices.
|
||||
self.remove_all_devices();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::Add(path) => {
|
||||
self.add_device(path);
|
||||
}
|
||||
Event::Remove(path) => {
|
||||
self.remove_device(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_device(&mut self, path: OsString) {
|
||||
let f = self.new_device_cb.clone();
|
||||
let key = path.clone();
|
||||
|
||||
let runloop = RunLoop::new(move |alive| {
|
||||
if alive() {
|
||||
f(path, alive);
|
||||
}
|
||||
});
|
||||
|
||||
if let Ok(runloop) = runloop {
|
||||
self.runloops.insert(key, runloop);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_device(&mut self, path: OsString) {
|
||||
if let Some(runloop) = self.runloops.remove(&path) {
|
||||
runloop.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_all_devices(&mut self) {
|
||||
while !self.runloops.is_empty() {
|
||||
let path = self.runloops.keys().next().unwrap().clone();
|
||||
self.remove_device(path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use platform::monitor::Monitor;
|
||||
use runloop::RunLoop;
|
||||
use std::ffi::OsString;
|
||||
use util::OnceCallback;
|
||||
|
||||
pub struct Transaction {
|
||||
// Handle to the thread loop.
|
||||
thread: Option<RunLoop>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn new<F, T>(
|
||||
timeout: u64,
|
||||
callback: OnceCallback<T>,
|
||||
new_device_cb: F,
|
||||
) -> Result<Self, ::Error>
|
||||
where
|
||||
F: Fn(OsString, &Fn() -> bool) + Sync + Send + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
// Create a new device monitor.
|
||||
let mut monitor = Monitor::new(new_device_cb);
|
||||
|
||||
// Start polling for new devices.
|
||||
try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
|
||||
|
||||
// Send an error, if the callback wasn't called already.
|
||||
callback.call(Err(::Error::NotAllowed));
|
||||
},
|
||||
timeout,
|
||||
).map_err(|_| ::Error::Unknown)?;
|
||||
|
||||
Ok(Self {
|
||||
thread: Some(thread),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self) {
|
||||
// This must never be None.
|
||||
self.thread.take().unwrap().cancel();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use std::io;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::ptr;
|
||||
|
||||
use hidproto::*;
|
||||
use util::from_unix_result;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct GenDescriptor {
|
||||
ugd_data: *mut u8,
|
||||
ugd_lang_id: u16,
|
||||
ugd_maxlen: u16,
|
||||
ugd_actlen: u16,
|
||||
ugd_offset: u16,
|
||||
ugd_config_index: u8,
|
||||
ugd_string_index: u8,
|
||||
ugd_iface_index: u8,
|
||||
ugd_altif_index: u8,
|
||||
ugd_endpt_index: u8,
|
||||
ugd_report_index: u8,
|
||||
reserved: [u8; 16],
|
||||
}
|
||||
|
||||
impl Default for GenDescriptor {
|
||||
fn default() -> GenDescriptor {
|
||||
GenDescriptor {
|
||||
ugd_data: ptr::null_mut(),
|
||||
ugd_lang_id: 0,
|
||||
ugd_maxlen: 65535,
|
||||
ugd_actlen: 0,
|
||||
ugd_offset: 0,
|
||||
ugd_config_index: 0,
|
||||
ugd_string_index: 0,
|
||||
ugd_iface_index: 0,
|
||||
ugd_altif_index: 0,
|
||||
ugd_endpt_index: 0,
|
||||
ugd_report_index: 0,
|
||||
reserved: [0; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IOWR: u32 = 0x40000000 | 0x80000000;
|
||||
|
||||
const IOCPARM_SHIFT: u32 = 13;
|
||||
const IOCPARM_MASK: u32 = ((1 << IOCPARM_SHIFT) - 1);
|
||||
|
||||
const TYPESHIFT: u32 = 8;
|
||||
const SIZESHIFT: u32 = 16;
|
||||
|
||||
macro_rules! ioctl {
|
||||
($dir:expr, $name:ident, $ioty:expr, $nr:expr, $size:expr; $ty:ty) => {
|
||||
pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
|
||||
let ioc = ($dir as u32)
|
||||
| (($size as u32 & IOCPARM_MASK) << SIZESHIFT)
|
||||
| (($ioty as u32) << TYPESHIFT)
|
||||
| ($nr as u32);
|
||||
from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// https://github.com/freebsd/freebsd/blob/master/sys/dev/usb/usb_ioctl.h
|
||||
ioctl!(IOWR, usb_get_report_desc, b'U', 21, 32; /*struct*/ GenDescriptor);
|
||||
|
||||
fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
|
||||
let mut desc = GenDescriptor::default();
|
||||
let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
|
||||
desc.ugd_maxlen = desc.ugd_actlen;
|
||||
let mut value = Vec::with_capacity(desc.ugd_actlen as usize);
|
||||
unsafe {
|
||||
value.set_len(desc.ugd_actlen as usize);
|
||||
}
|
||||
desc.ugd_data = value.as_mut_ptr();
|
||||
let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
|
||||
Ok(ReportDescriptor { value })
|
||||
}
|
||||
|
||||
pub fn is_u2f_device(fd: RawFd) -> bool {
|
||||
match read_report_descriptor(fd) {
|
||||
Ok(desc) => has_fido_usage(desc),
|
||||
Err(_) => false, // Upon failure, just say it's not a U2F device.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Shared code for platforms that use raw HID access (Linux, FreeBSD, etc.)
|
||||
|
||||
use std::mem;
|
||||
|
||||
use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
|
||||
|
||||
// The 4 MSBs (the tag) are set when it's a long item.
|
||||
const HID_MASK_LONG_ITEM_TAG: u8 = 0b11110000;
|
||||
// The 2 LSBs denote the size of a short item.
|
||||
const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b00000011;
|
||||
// The 6 MSBs denote the tag (4) and type (2).
|
||||
const HID_MASK_ITEM_TAGTYPE: u8 = 0b11111100;
|
||||
// tag=0000, type=10 (local)
|
||||
const HID_ITEM_TAGTYPE_USAGE: u8 = 0b00001000;
|
||||
// tag=0000, type=01 (global)
|
||||
const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b00000100;
|
||||
|
||||
pub struct ReportDescriptor {
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ReportDescriptor {
|
||||
fn iter(self) -> ReportDescriptorIterator {
|
||||
ReportDescriptorIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Data {
|
||||
UsagePage { data: u32 },
|
||||
Usage { data: u32 },
|
||||
}
|
||||
|
||||
pub struct ReportDescriptorIterator {
|
||||
desc: ReportDescriptor,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl ReportDescriptorIterator {
|
||||
fn new(desc: ReportDescriptor) -> Self {
|
||||
Self { desc, pos: 0 }
|
||||
}
|
||||
|
||||
fn next_item(&mut self) -> Option<Data> {
|
||||
let item = get_hid_item(&self.desc.value[self.pos..]);
|
||||
if item.is_none() {
|
||||
self.pos = self.desc.value.len(); // Close, invalid data.
|
||||
return None;
|
||||
}
|
||||
|
||||
let (tag_type, key_len, data) = item.unwrap();
|
||||
|
||||
// Advance if we have a valid item.
|
||||
self.pos += key_len + data.len();
|
||||
|
||||
// We only check short items.
|
||||
if key_len > 1 {
|
||||
return None; // Check next item.
|
||||
}
|
||||
|
||||
// Short items have max. length of 4 bytes.
|
||||
assert!(data.len() <= mem::size_of::<u32>());
|
||||
|
||||
// Convert data bytes to a uint.
|
||||
let data = read_uint_le(data);
|
||||
|
||||
match tag_type {
|
||||
HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
|
||||
HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ReportDescriptorIterator {
|
||||
type Item = Data;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.pos >= self.desc.value.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.next_item().or_else(|| self.next())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||
if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
|
||||
get_hid_long_item(buf)
|
||||
} else {
|
||||
get_hid_short_item(buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||
// A valid long item has at least three bytes.
|
||||
if buf.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let len = buf[1] as usize;
|
||||
|
||||
// Ensure that there are enough bytes left in the buffer.
|
||||
if len > buf.len() - 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((buf[2], 3 /* key length */, &buf[3..]))
|
||||
}
|
||||
|
||||
fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||
// This is a short item. The bottom two bits of the key
|
||||
// contain the length of the data section (value) for this key.
|
||||
let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
|
||||
s @ 0...2 => s as usize,
|
||||
_ => 4, /* _ == 3 */
|
||||
};
|
||||
|
||||
// Ensure that there are enough bytes left in the buffer.
|
||||
if len > buf.len() - 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
buf[0] & HID_MASK_ITEM_TAGTYPE,
|
||||
1, /* key length */
|
||||
&buf[1..1 + len],
|
||||
))
|
||||
}
|
||||
|
||||
fn read_uint_le(buf: &[u8]) -> u32 {
|
||||
assert!(buf.len() <= 4);
|
||||
// Parse the number in little endian byte order.
|
||||
buf.iter().rev().fold(0, |num, b| (num << 8) | (*b as u32))
|
||||
}
|
||||
|
||||
pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
|
||||
let mut usage_page = None;
|
||||
let mut usage = None;
|
||||
|
||||
for data in desc.iter() {
|
||||
match data {
|
||||
Data::UsagePage { data } => usage_page = Some(data),
|
||||
Data::Usage { data } => usage = Some(data),
|
||||
}
|
||||
|
||||
// Check the values we found.
|
||||
if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
|
||||
return usage_page == FIDO_USAGE_PAGE as u32 && usage == FIDO_USAGE_U2FHID as u32;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
|
@ -5,6 +5,9 @@
|
|||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub mod hidproto;
|
||||
|
||||
#[cfg(any(target_os = "linux"))]
|
||||
extern crate libudev;
|
||||
|
||||
|
@ -12,6 +15,13 @@ extern crate libudev;
|
|||
#[path = "linux/mod.rs"]
|
||||
pub mod platform;
|
||||
|
||||
#[cfg(any(target_os = "freebsd"))]
|
||||
extern crate devd_rs;
|
||||
|
||||
#[cfg(any(target_os = "freebsd"))]
|
||||
#[path = "freebsd/mod.rs"]
|
||||
pub mod platform;
|
||||
|
||||
#[cfg(any(target_os = "macos"))]
|
||||
extern crate core_foundation_sys;
|
||||
|
||||
|
@ -26,7 +36,11 @@ pub mod platform;
|
|||
#[path = "windows/mod.rs"]
|
||||
pub mod platform;
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
#[cfg(
|
||||
not(
|
||||
any(target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "windows")
|
||||
)
|
||||
)]
|
||||
#[path = "stub/mod.rs"]
|
||||
pub mod platform;
|
||||
|
||||
|
|
|
@ -8,22 +8,16 @@ use std::io;
|
|||
use std::mem;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
|
||||
use hidproto::*;
|
||||
use util::{from_unix_result, io_err};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(C)]
|
||||
pub struct ReportDescriptor {
|
||||
pub struct LinuxReportDescriptor {
|
||||
size: ::libc::c_int,
|
||||
value: [u8; 4096],
|
||||
}
|
||||
|
||||
impl ReportDescriptor {
|
||||
fn iter(self) -> ReportDescriptorIterator {
|
||||
ReportDescriptorIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
const NRBITS: u32 = 8;
|
||||
const TYPEBITS: u32 = 8;
|
||||
|
||||
|
@ -35,17 +29,6 @@ const TYPESHIFT: u32 = NRSHIFT + NRBITS as u32;
|
|||
const SIZESHIFT: u32 = TYPESHIFT + TYPEBITS as u32;
|
||||
const DIRSHIFT: u32 = SIZESHIFT + SIZEBITS as u32;
|
||||
|
||||
// The 4 MSBs (the tag) are set when it's a long item.
|
||||
const HID_MASK_LONG_ITEM_TAG: u8 = 0b11110000;
|
||||
// The 2 LSBs denote the size of a short item.
|
||||
const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b00000011;
|
||||
// The 6 MSBs denote the tag (4) and type (2).
|
||||
const HID_MASK_ITEM_TAGTYPE: u8 = 0b11111100;
|
||||
// tag=0000, type=10 (local)
|
||||
const HID_ITEM_TAGTYPE_USAGE: u8 = 0b00001000;
|
||||
// tag=0000, type=01 (global)
|
||||
const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b00000100;
|
||||
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/hid.h
|
||||
const HID_MAX_DESCRIPTOR_SIZE: usize = 4096;
|
||||
|
||||
|
@ -53,8 +36,10 @@ macro_rules! ioctl {
|
|||
($dir:expr, $name:ident, $ioty:expr, $nr:expr; $ty:ty) => {
|
||||
pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
|
||||
let size = mem::size_of::<$ty>();
|
||||
let ioc = (($dir as u32) << DIRSHIFT) | (($ioty as u32) << TYPESHIFT)
|
||||
| (($nr as u32) << NRSHIFT) | ((size as u32) << SIZESHIFT);
|
||||
let ioc = (($dir as u32) << DIRSHIFT)
|
||||
| (($ioty as u32) << TYPESHIFT)
|
||||
| (($nr as u32) << NRSHIFT)
|
||||
| ((size as u32) << SIZESHIFT);
|
||||
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
type IocType = libc::c_ulong;
|
||||
|
@ -68,115 +53,7 @@ macro_rules! ioctl {
|
|||
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/hidraw.h
|
||||
ioctl!(READ, hidiocgrdescsize, b'H', 0x01; ::libc::c_int);
|
||||
ioctl!(READ, hidiocgrdesc, b'H', 0x02; /*struct*/ ReportDescriptor);
|
||||
|
||||
enum Data {
|
||||
UsagePage { data: u32 },
|
||||
Usage { data: u32 },
|
||||
}
|
||||
|
||||
struct ReportDescriptorIterator {
|
||||
desc: ReportDescriptor,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl ReportDescriptorIterator {
|
||||
fn new(desc: ReportDescriptor) -> Self {
|
||||
Self { desc, pos: 0 }
|
||||
}
|
||||
|
||||
fn next_item(&mut self) -> Option<Data> {
|
||||
let item = get_hid_item(&self.desc.value[self.pos..]);
|
||||
if item.is_none() {
|
||||
self.pos = self.desc.size as usize; // Close, invalid data.
|
||||
return None;
|
||||
}
|
||||
|
||||
let (tag_type, key_len, data) = item.unwrap();
|
||||
|
||||
// Advance if we have a valid item.
|
||||
self.pos += key_len + data.len();
|
||||
|
||||
// We only check short items.
|
||||
if key_len > 1 {
|
||||
return None; // Check next item.
|
||||
}
|
||||
|
||||
// Short items have max. length of 4 bytes.
|
||||
assert!(data.len() <= mem::size_of::<u32>());
|
||||
|
||||
// Convert data bytes to a uint.
|
||||
let data = read_uint_le(data);
|
||||
|
||||
match tag_type {
|
||||
HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
|
||||
HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ReportDescriptorIterator {
|
||||
type Item = Data;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.pos >= self.desc.size as usize {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.next_item().or_else(|| self.next())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||
if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
|
||||
get_hid_long_item(buf)
|
||||
} else {
|
||||
get_hid_short_item(buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||
// A valid long item has at least three bytes.
|
||||
if buf.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let len = buf[1] as usize;
|
||||
|
||||
// Ensure that there are enough bytes left in the buffer.
|
||||
if len > buf.len() - 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((buf[2], 3 /* key length */, &buf[3..]))
|
||||
}
|
||||
|
||||
fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||
// This is a short item. The bottom two bits of the key
|
||||
// contain the length of the data section (value) for this key.
|
||||
let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
|
||||
s @ 0...2 => s as usize,
|
||||
_ => 4, /* _ == 3 */
|
||||
};
|
||||
|
||||
// Ensure that there are enough bytes left in the buffer.
|
||||
if len > buf.len() - 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
buf[0] & HID_MASK_ITEM_TAGTYPE,
|
||||
1, /* key length */
|
||||
&buf[1..1 + len],
|
||||
))
|
||||
}
|
||||
|
||||
fn read_uint_le(buf: &[u8]) -> u32 {
|
||||
assert!(buf.len() <= 4);
|
||||
// Parse the number in little endian byte order.
|
||||
buf.iter().rev().fold(0, |num, b| (num << 8) | (*b as u32))
|
||||
}
|
||||
ioctl!(READ, hidiocgrdesc, b'H', 0x02; /*struct*/ LinuxReportDescriptor);
|
||||
|
||||
pub fn is_u2f_device(fd: RawFd) -> bool {
|
||||
match read_report_descriptor(fd) {
|
||||
|
@ -186,7 +63,7 @@ pub fn is_u2f_device(fd: RawFd) -> bool {
|
|||
}
|
||||
|
||||
fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
|
||||
let mut desc = ReportDescriptor {
|
||||
let mut desc = LinuxReportDescriptor {
|
||||
size: 0,
|
||||
value: [0; HID_MAX_DESCRIPTOR_SIZE],
|
||||
};
|
||||
|
@ -197,24 +74,7 @@ fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
|
|||
}
|
||||
|
||||
let _ = unsafe { hidiocgrdesc(fd, &mut desc)? };
|
||||
Ok(desc)
|
||||
}
|
||||
|
||||
fn has_fido_usage(desc: ReportDescriptor) -> bool {
|
||||
let mut usage_page = None;
|
||||
let mut usage = None;
|
||||
|
||||
for data in desc.iter() {
|
||||
match data {
|
||||
Data::UsagePage { data } => usage_page = Some(data),
|
||||
Data::Usage { data } => usage = Some(data),
|
||||
}
|
||||
|
||||
// Check the values we found.
|
||||
if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
|
||||
return usage_page == FIDO_USAGE_PAGE as u32 && usage == FIDO_USAGE_U2FHID as u32;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
let mut value = Vec::from(&desc.value[..]);
|
||||
value.truncate(desc.size as usize);
|
||||
Ok(ReportDescriptor { value })
|
||||
}
|
||||
|
|
|
@ -8,15 +8,15 @@ extern crate core_foundation_sys;
|
|||
extern crate libc;
|
||||
|
||||
use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
|
||||
use core_foundation::dictionary::*;
|
||||
use core_foundation::number::*;
|
||||
use core_foundation::string::*;
|
||||
use core_foundation_sys::base::*;
|
||||
use core_foundation_sys::dictionary::*;
|
||||
use core_foundation_sys::runloop::*;
|
||||
use core_foundation_sys::string::*;
|
||||
use core_foundation::dictionary::*;
|
||||
use core_foundation::string::*;
|
||||
use core_foundation::number::*;
|
||||
use std::os::raw::c_void;
|
||||
use std::ops::Deref;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
type IOOptionBits = u32;
|
||||
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
extern crate libc;
|
||||
extern crate log;
|
||||
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation_sys::base::*;
|
||||
use core_foundation_sys::runloop::*;
|
||||
use core_foundation::base::TCFType;
|
||||
use std::os::raw::c_void;
|
||||
use platform::iokit::*;
|
||||
use runloop::RunLoop;
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::{io, slice};
|
||||
use util::io_err;
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
extern crate libc;
|
||||
|
||||
use core_foundation_sys::runloop::*;
|
||||
use std::os::raw::c_void;
|
||||
use platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop};
|
||||
use platform::monitor::Monitor;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread;
|
||||
use util::OnceCallback;
|
||||
|
|
|
@ -165,9 +165,7 @@ impl StateMachine {
|
|||
// Aggregate distinct transports from all given credentials.
|
||||
let transports = key_handles
|
||||
.iter()
|
||||
.fold(::AuthenticatorTransports::empty(), |t, k| {
|
||||
t | k.transports
|
||||
});
|
||||
.fold(::AuthenticatorTransports::empty(), |t, k| t | k.transports);
|
||||
|
||||
// We currently only support USB. If the RP specifies transports
|
||||
// and doesn't include USB it's probably lying.
|
||||
|
|
|
@ -46,6 +46,16 @@ pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "freebsd"))]
|
||||
pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
|
||||
if rv.is_negative() {
|
||||
let errno = unsafe { *libc::__error() };
|
||||
Err(io::Error::from_raw_os_error(errno))
|
||||
} else {
|
||||
Ok(rv)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn io_err(msg: &str) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче