Bug 1388843 - Part 1: Copy u2f-hid-rs into dom/webauthn/ r=gerv,qdot

This commit is contained in:
Tim Taubert 2017-08-09 21:16:49 +02:00
Родитель 7c3902243d
Коммит dd24dc77e0
51 изменённых файлов: 4250 добавлений и 0 удалений

9
dom/webauthn/u2f-hid-rs/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
.DS_Store

Просмотреть файл

@ -0,0 +1,30 @@
sudo: false
language: rust
cache: cargo
rust:
- stable
- beta
- nightly
addons:
apt:
packages:
- build-essential
- libudev-dev
before_install:
- pkg-config --list-all
- pkg-config --libs libudev
- pkg-config --modversion libudev
- cargo install rustfmt || true
script:
- |
if [ "$TRAVIS_RUST_VERSION" == "nightly" ] ; then
export ASAN_OPTIONS="detect_odr_violation=1:leak_check_at_exit=0:detect_leaks=0"
export RUSTFLAGS="-Z sanitizer=address"
fi
- |
cargo fmt -- --write-mode=diff &&
cargo build &&
cargo test

Просмотреть файл

@ -0,0 +1,25 @@
[package]
name = "u2fhid"
version = "0.1.0"
authors = ["Kyle Machulis <kyle@nonpolynomial.com>", "J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>"]
build = "build.rs"
[target.'cfg(target_os = "linux")'.dependencies]
libudev = "^0.2"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation-sys = "0.3.1"
[target.'cfg(target_os = "windows")'.dependencies]
winapi = "0.2.8"
[dependencies]
rand = "0.3"
log = "0.3"
env_logger = "0.4.1"
libc = "^0.2"
boxfnonce = "0.0.3"
[dev-dependencies]
rust-crypto = "^0.2"
base64 = "^0.4"

Просмотреть файл

@ -0,0 +1,374 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

Просмотреть файл

@ -0,0 +1,50 @@
# A Rust HID library for interacting with U2F Security Keys
[![Build Status](https://travis-ci.org/jcjones/u2f-hid-rs.svg?branch=master)](https://travis-ci.org/jcjones/u2f-hid-rs)
![Maturity Level](https://img.shields.io/badge/maturity-beta-yellow.svg)
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 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).
This library currently focuses on U2F security keys, but is expected to be extended to
support additional protocols and transports.
## Usage
There's only a simple example function that tries to register and sign right now. It uses
[env_logger](http://rust-lang-nursery.github.io/log/env_logger/) for logging, which you
configure with the `RUST_LOG` environment variable:
```
cargo build
RUST_LOG=debug cargo run --example main
```
Proper usage should be to call into this library from something else - e.g., Firefox. There are
some [C headers exposed for the purpose](u2f-hid-rs/blob/master/src/u2fhid-capi.h).
## Tests
There are some tests of the cross-platform runloop logic and the protocol decoder:
```
cargo test
```
## Fuzzing
There are fuzzers for the USB protocol reader, basically fuzzing inputs from the HID layer.
There are not (yet) fuzzers for the C API used by callers (such as Gecko).
To fuzz, you will need cargo-fuzz (the latest version from GitHub) as well as Rust Nightly.
```
rustup install nightly
cargo install --git https://github.com/rust-fuzz/cargo-fuzz/
rustup run nightly cargo fuzz run u2f_read -- -max_len=512
rustup run nightly cargo fuzz run u2f_read_write -- -max_len=512
```

Просмотреть файл

@ -0,0 +1,8 @@
/* 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/. */
fn main() {
#[cfg(any(target_os = "macos"))]
println!("cargo:rustc-link-lib=framework=IOKit");
}

Просмотреть файл

@ -0,0 +1,79 @@
/* 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 base64;
extern crate crypto;
extern crate u2fhid;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use std::io;
use std::sync::mpsc::channel;
use u2fhid::U2FManager;
extern crate log;
extern crate env_logger;
fn u2f_get_key_handle_from_register_response(register_response: &Vec<u8>) -> io::Result<Vec<u8>> {
if register_response[0] != 0x05 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Reserved byte not set correctly",
));
}
let key_handle_len = register_response[66] as usize;
let mut public_key = register_response.clone();
let mut key_handle = public_key.split_off(67);
let _attestation = key_handle.split_off(key_handle_len);
Ok(key_handle)
}
fn main() {
env_logger::init().expect("Cannot start logger");
println!("Asking a security key to register now...");
let challenge_str =
format!("{}{}",
r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"#);
let mut challenge = Sha256::new();
challenge.input_str(&challenge_str);
let mut chall_bytes: Vec<u8> = vec![0; challenge.output_bytes()];
challenge.result(&mut chall_bytes);
let mut application = Sha256::new();
application.input_str("http://demo.yubico.com");
let mut app_bytes: Vec<u8> = vec![0; application.output_bytes()];
application.result(&mut app_bytes);
let manager = U2FManager::new().unwrap();
let (tx, rx) = channel();
manager
.register(15_000, chall_bytes.clone(), app_bytes.clone(), move |rv| {
tx.send(rv.unwrap()).unwrap();
})
.unwrap();
let register_data = rx.recv().unwrap();
println!("Register result: {}", base64::encode(&register_data));
println!("Asking a security key to sign now, with the data from the register...");
let key_handle = u2f_get_key_handle_from_register_response(&register_data).unwrap();
let (tx, rx) = channel();
manager
.sign(
15_000,
chall_bytes,
app_bytes,
vec![key_handle],
move |rv| { tx.send(rv.unwrap()).unwrap(); },
)
.unwrap();
let (_, sign_data) = rx.recv().unwrap();
println!("Sign result: {}", base64::encode(&sign_data));
println!("Done.");
}

2
dom/webauthn/u2f-hid-rs/fuzz/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
target
artifacts

Просмотреть файл

@ -0,0 +1,29 @@
[package]
name = "u2fhid-fuzz"
version = "0.0.1"
authors = ["Automatically generated"]
publish = false
[package.metadata]
cargo-fuzz = true
[dependencies]
rand = "0.3"
[dependencies.u2fhid]
path = ".."
[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "u2f_read"
path = "fuzz_targets/u2f_read.rs"
[[bin]]
name = "u2f_read_write"
path = "fuzz_targets/u2f_read_write.rs"

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,65 @@
/* 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/. */
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate rand;
extern crate u2fhid;
use rand::{thread_rng, Rng};
use std::{cmp, io};
use u2fhid::{CID_BROADCAST, HID_RPT_SIZE};
use u2fhid::{U2FDevice, sendrecv};
struct TestDevice<'a> {
cid: [u8; 4],
data: &'a [u8]
}
impl<'a> TestDevice<'a> {
pub fn new(data: &'a [u8]) -> TestDevice {
TestDevice {
cid: CID_BROADCAST,
data,
}
}
}
impl<'a> io::Read for TestDevice<'a> {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
assert!(bytes.len() == HID_RPT_SIZE);
let max = cmp::min(self.data.len(), HID_RPT_SIZE);
bytes[..max].copy_from_slice(&self.data[..max]);
self.data = &self.data[max..];
Ok(max)
}
}
impl<'a> io::Write for TestDevice<'a> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
assert!(bytes.len() == HID_RPT_SIZE + 1);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<'a> U2FDevice for TestDevice<'a> {
fn get_cid<'b>(&'b self) -> &'b [u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
}
fuzz_target!(|data: &[u8]| {
let mut dev = TestDevice::new(data);
let cmd = thread_rng().gen::<u8>();
let _ = sendrecv(&mut dev, cmd, data);
});

Просмотреть файл

@ -0,0 +1,67 @@
/* 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/. */
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate rand;
extern crate u2fhid;
use rand::{thread_rng, Rng};
use std::{cmp, io};
use u2fhid::{CID_BROADCAST, HID_RPT_SIZE};
use u2fhid::{U2FDevice, sendrecv};
struct TestDevice {
cid: [u8; 4],
data: Vec<u8>,
}
impl TestDevice {
pub fn new() -> TestDevice {
TestDevice {
cid: CID_BROADCAST,
data: vec!(),
}
}
}
impl io::Read for TestDevice {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
assert!(bytes.len() == HID_RPT_SIZE);
let max = cmp::min(self.data.len(), HID_RPT_SIZE);
bytes[..max].copy_from_slice(&self.data[..max]);
self.data = self.data[max..].to_vec();
Ok(max)
}
}
impl io::Write for TestDevice {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
assert!(bytes.len() == HID_RPT_SIZE + 1);
self.data.extend_from_slice(&bytes[1..]);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl U2FDevice for TestDevice {
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
}
fuzz_target!(|data: &[u8]| {
let mut dev = TestDevice::new();
let cmd = thread_rng().gen::<u8>();
let res = sendrecv(&mut dev, cmd, data);
assert_eq!(data, &res.unwrap()[..]);
});

Просмотреть файл

@ -0,0 +1,2 @@
comment_width = 200
wrap_comments = true

Просмотреть файл

@ -0,0 +1,44 @@
/* 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/. */
// No-op module to permit compiling token HID support for Android, where
// no results are returned.
#![allow(dead_code)]
use util::OnceCallback;
pub struct PlatformManager {}
impl PlatformManager {
pub fn new() -> Self {
Self {}
}
pub fn register(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
callback: OnceCallback<Vec<u8>>,
) {
// No-op on Android
}
pub fn sign(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
) {
// No-op on Android
}
// This blocks.
pub fn cancel(&mut self) {
// No-op on Android
}
}

Просмотреть файл

@ -0,0 +1,197 @@
/* 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 libc::size_t;
use rand::{thread_rng, Rng};
use std::collections::HashMap;
use std::{ptr, slice};
use U2FManager;
type U2FKeyHandles = Vec<Vec<u8>>;
type U2FResult = HashMap<u8, Vec<u8>>;
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
const RESBUF_ID_REGISTRATION: u8 = 0;
const RESBUF_ID_KEYHANDLE: u8 = 1;
const RESBUF_ID_SIGNATURE: u8 = 2;
// Generates a new 64-bit transaction id with collision probability 2^-32.
fn new_tid() -> u64 {
thread_rng().gen::<u64>()
}
unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
slice::from_raw_parts(ptr, len).to_vec()
}
#[no_mangle]
pub extern "C" fn rust_u2f_mgr_new() -> *mut U2FManager {
if let Ok(mgr) = U2FManager::new() {
Box::into_raw(Box::new(mgr))
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut U2FManager) {
if !mgr.is_null() {
Box::from_raw(mgr);
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
Box::into_raw(Box::new(vec![]))
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_khs_add(
khs: *mut U2FKeyHandles,
key_handle_ptr: *const u8,
key_handle_len: usize,
) {
(*khs).push(from_raw(key_handle_ptr, key_handle_len));
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
if !khs.is_null() {
Box::from_raw(khs);
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_resbuf_length(
res: *const U2FResult,
bid: u8,
len: *mut size_t,
) -> bool {
if res.is_null() {
return false;
}
if let Some(buf) = (*res).get(&bid) {
*len = buf.len();
return true;
}
false
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_resbuf_copy(
res: *const U2FResult,
bid: u8,
dst: *mut u8,
) -> bool {
if res.is_null() {
return false;
}
if let Some(buf) = (*res).get(&bid) {
ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len());
return true;
}
false
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) {
if !res.is_null() {
Box::from_raw(res);
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_register(
mgr: *mut U2FManager,
timeout: u64,
callback: U2FCallback,
challenge_ptr: *const u8,
challenge_len: usize,
application_ptr: *const u8,
application_len: usize,
) -> u64 {
if mgr.is_null() {
return 0;
}
// Check buffers.
if challenge_ptr.is_null() || application_ptr.is_null() {
return 0;
}
let challenge = from_raw(challenge_ptr, challenge_len);
let application = from_raw(application_ptr, application_len);
let tid = new_tid();
let res = (*mgr).register(timeout, challenge, application, move |rv| {
if let Ok(registration) = rv {
let mut result = U2FResult::new();
result.insert(RESBUF_ID_REGISTRATION, registration);
callback(tid, Box::into_raw(Box::new(result)));
} else {
callback(tid, ptr::null_mut());
};
});
if res.is_ok() { tid } else { 0 }
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_sign(
mgr: *mut U2FManager,
timeout: u64,
callback: U2FCallback,
challenge_ptr: *const u8,
challenge_len: usize,
application_ptr: *const u8,
application_len: usize,
khs: *const U2FKeyHandles,
) -> u64 {
if mgr.is_null() || khs.is_null() {
return 0;
}
// Check buffers.
if challenge_ptr.is_null() || application_ptr.is_null() {
return 0;
}
// Need at least one key handle.
if (*khs).len() < 1 {
return 0;
}
let challenge = from_raw(challenge_ptr, challenge_len);
let application = from_raw(application_ptr, application_len);
let key_handles = (*khs).clone();
let tid = new_tid();
let res = (*mgr).sign(timeout, challenge, application, key_handles, move |rv| {
if let Ok((key_handle, signature)) = rv {
let mut result = U2FResult::new();
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
result.insert(RESBUF_ID_SIGNATURE, signature);
callback(tid, Box::into_raw(Box::new(result)));
} else {
callback(tid, ptr::null_mut());
};
});
if res.is_ok() { tid } else { 0 }
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut U2FManager) -> u64 {
if !mgr.is_null() {
// Ignore return value.
let _ = (*mgr).cancel();
}
new_tid()
}

Просмотреть файл

@ -0,0 +1,78 @@
/* 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/. */
// Allow dead code in this module, since it's all packet consts anyways.
#![allow(dead_code)]
pub const HID_RPT_SIZE: usize = 64;
pub const U2FAPDUHEADER_SIZE: usize = 7;
pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
pub const TYPE_MASK: u8 = 0x80;
pub const TYPE_INIT: u8 = 0x80;
pub const TYPE_CONT: u8 = 0x80;
// Size of data chunk expected in U2F Init USB HID Packets
pub const INIT_DATA_SIZE: usize = HID_RPT_SIZE - 7;
// Size of data chunk expected in U2F Cont USB HID Packets
pub const CONT_DATA_SIZE: usize = HID_RPT_SIZE - 5;
pub const PARAMETER_SIZE: usize = 32;
pub const FIDO_USAGE_PAGE: u16 = 0xf1d0; // FIDO alliance HID usage page
pub const FIDO_USAGE_U2FHID: u16 = 0x01; // U2FHID usage for top-level collection
pub const FIDO_USAGE_DATA_IN: u8 = 0x20; // Raw IN data report
pub const FIDO_USAGE_DATA_OUT: u8 = 0x21; // Raw OUT data report
// General pub constants
pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation version
pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms
pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms
// U2FHID native commands
pub const U2FHID_PING: u8 = (TYPE_INIT | 0x01); // Echo data through local processor only
pub const U2FHID_MSG: u8 = (TYPE_INIT | 0x03); // Send U2F message frame
pub const U2FHID_LOCK: u8 = (TYPE_INIT | 0x04); // Send lock channel command
pub const U2FHID_INIT: u8 = (TYPE_INIT | 0x06); // Channel initialization
pub const U2FHID_WINK: u8 = (TYPE_INIT | 0x08); // Send device identification wink
pub const U2FHID_ERROR: u8 = (TYPE_INIT | 0x3f); // Error response
// U2FHID_MSG commands
pub const U2F_VENDOR_FIRST: u8 = (TYPE_INIT | 0x40); // First vendor defined command
pub const U2F_VENDOR_LAST: u8 = (TYPE_INIT | 0x7f); // Last vendor defined command
pub const U2F_REGISTER: u8 = 0x01; // Registration command
pub const U2F_AUTHENTICATE: u8 = 0x02; // Authenticate/sign command
pub const U2F_VERSION: u8 = 0x03; // Read version string command
// U2F_REGISTER command defines
pub const U2F_REGISTER_ID: u8 = 0x05; // Version 2 registration identifier
pub const U2F_REGISTER_HASH_ID: u8 = 0x00; // Version 2 hash identintifier
// U2F_AUTHENTICATE command defines
pub const U2F_REQUEST_USER_PRESENCE: u8 = 0x03; // Verify user presence and sign
pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is registered
// U2FHID_INIT command defines
pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge
pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command
pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command
// Low-level error codes. Return as negatives.
pub const ERR_NONE: u8 = 0x00; // No error
pub const ERR_INVALID_CMD: u8 = 0x01; // Invalid command
pub const ERR_INVALID_PAR: u8 = 0x02; // Invalid parameter
pub const ERR_INVALID_LEN: u8 = 0x03; // Invalid message length
pub const ERR_INVALID_SEQ: u8 = 0x04; // Invalid message sequencing
pub const ERR_MSG_TIMEOUT: u8 = 0x05; // Message has timed out
pub const ERR_CHANNEL_BUSY: u8 = 0x06; // Channel busy
pub const ERR_LOCK_REQUIRED: u8 = 0x0a; // Command requires channel lock
pub const ERR_INVALID_CID: u8 = 0x0b; // Command not allowed on this cid
pub const ERR_OTHER: u8 = 0x7f; // Other unspecified error
// These are ISO 7816-4 defined response status words.
pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00];
pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85];
pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80];
pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00];

Просмотреть файл

@ -0,0 +1,52 @@
/* 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/. */
#[macro_use]
mod util;
#[cfg(any(target_os = "linux"))]
extern crate libudev;
#[cfg(any(target_os = "linux"))]
#[path = "linux/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "macos"))]
extern crate core_foundation_sys;
#[cfg(any(target_os = "macos"))]
#[path = "macos/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "windows"))]
#[path = "windows/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "android"))]
#[path = "android/mod.rs"]
pub mod platform;
#[macro_use]
extern crate log;
extern crate rand;
extern crate libc;
extern crate boxfnonce;
mod consts;
mod runloop;
mod u2ftypes;
mod u2fprotocol;
mod manager;
pub use manager::U2FManager;
mod capi;
pub use capi::*;
#[cfg(fuzzing)]
pub use u2fprotocol::*;
#[cfg(fuzzing)]
pub use u2ftypes::*;
#[cfg(fuzzing)]
pub use consts::*;

Просмотреть файл

@ -0,0 +1,83 @@
/* 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::hidraw;
use util::{from_unix_result, to_io_err};
use u2ftypes::U2FDevice;
#[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()).map_err(to_io_err)?;
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 {
hidraw::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 bufp = buf.as_ptr() as *const libc::c_void;
let rv = unsafe { libc::write(self.fd, bufp, buf.len()) };
from_unix_result(rv as usize)
}
// 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,50 @@
/* 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 std::collections::hash_map::ValuesMut;
use std::collections::HashMap;
use std::ffi::OsString;
use platform::device::Device;
use platform::monitor::Event;
use u2fprotocol::u2f_init_device;
pub struct DeviceMap {
map: HashMap<OsString, Device>,
}
impl DeviceMap {
pub fn new() -> Self {
Self { map: HashMap::new() }
}
pub fn values_mut(&mut self) -> ValuesMut<OsString, Device> {
self.map.values_mut()
}
pub fn process_event(&mut self, event: Event) {
match event {
Event::Add(path) => self.add(path),
Event::Remove(path) => self.remove(path),
}
}
fn add(&mut self, path: OsString) {
if self.map.contains_key(&path) {
return;
}
// Create and try to open the device.
if let Ok(mut dev) = Device::new(path.clone()) {
if dev.is_u2f() && u2f_init_device(&mut dev) {
self.map.insert(path, dev);
}
}
}
fn remove(&mut self, path: OsString) {
// Ignore errors.
let _ = self.map.remove(&path);
}
}

Просмотреть файл

@ -0,0 +1,216 @@
/* 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::mem;
use std::os::unix::io::RawFd;
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
use util::{from_unix_result, io_err};
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct ReportDescriptor {
size: ::libc::c_int,
value: [u8; 4096],
}
impl ReportDescriptor {
fn iter(self) -> ReportDescriptorIterator {
ReportDescriptorIterator::new(self)
}
}
const NRBITS: u32 = 8;
const TYPEBITS: u32 = 8;
const READ: u8 = 2;
const SIZEBITS: u8 = 14;
const NRSHIFT: u32 = 0;
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;
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);
from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val))
}
);
}
// 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))
}
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.
}
}
fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
let mut desc = ReportDescriptor {
size: 0,
value: [0; HID_MAX_DESCRIPTOR_SIZE],
};
let _ = unsafe { hidiocgrdescsize(fd, &mut desc.size)? };
if desc.size == 0 || desc.size as usize > desc.value.len() {
return Err(io_err("unexpected hidiocgrdescsize() result"));
}
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
}

Просмотреть файл

@ -0,0 +1,159 @@
/* 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 std::time::Duration;
use std::thread;
mod device;
mod devicemap;
mod hidraw;
mod monitor;
use consts::PARAMETER_SIZE;
use runloop::RunLoop;
use util::{io_err, OnceCallback};
use u2fprotocol::{u2f_is_keyhandle_valid, u2f_register, u2f_sign};
use self::devicemap::DeviceMap;
use self::monitor::Monitor;
pub struct PlatformManager {
// Handle to the thread loop.
thread: Option<RunLoop>,
}
impl PlatformManager {
pub fn new() -> Self {
Self { thread: None }
}
pub fn register(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
callback: OnceCallback<Vec<u8>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let thread = RunLoop::new(
move |alive| {
let mut devices = DeviceMap::new();
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
while alive() && monitor.alive() {
// Add/remove devices.
for event in monitor.events() {
devices.process_event(event);
}
// Try to register each device.
for device in devices.values_mut() {
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
callback.call(Ok(bytes));
return;
}
}
// Wait a little before trying again.
thread::sleep(Duration::from_millis(100));
}
callback.call(Err(io_err("aborted or timed out")));
},
timeout,
);
self.thread = Some(try_or!(
thread,
|_| cbc.call(Err(io_err("couldn't create runloop")))
));
}
pub fn sign(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let thread = RunLoop::new(
move |alive| {
let mut devices = DeviceMap::new();
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
while alive() && monitor.alive() {
// Add/remove devices.
for event in monitor.events() {
devices.process_event(event);
}
// Try signing with each device.
for key_handle in &key_handles {
for device in devices.values_mut() {
// Check if they key handle belongs to the current device.
let is_valid = match u2f_is_keyhandle_valid(
device,
&challenge,
&application,
key_handle,
) {
Ok(valid) => valid,
Err(_) => continue, // Skip this device for now.
};
if is_valid {
// If yes, try to sign.
if let Ok(bytes) = u2f_sign(
device,
&challenge,
&application,
key_handle,
)
{
callback.call(Ok((key_handle.clone(), bytes)));
return;
}
} else {
// If no, keep registering and blinking with bogus data
let blank = vec![0u8; PARAMETER_SIZE];
if let Ok(_) = u2f_register(device, &blank, &blank) {
callback.call(Err(io_err("invalid key")));
return;
}
}
}
}
// Wait a little before trying again.
thread::sleep(Duration::from_millis(100));
}
callback.call(Err(io_err("aborted or timed out")));
},
timeout,
);
self.thread = Some(try_or!(
thread,
|_| cbc.call(Err(io_err("couldn't create runloop")))
));
}
// This blocks.
pub fn cancel(&mut self) {
if let Some(thread) = self.thread.take() {
thread.cancel();
}
}
}

Просмотреть файл

@ -0,0 +1,123 @@
/* 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 libudev;
use libudev::EventType;
use libc::{c_int, c_short, c_ulong};
use std::ffi::OsString;
use std::io;
use std::os::unix::io::AsRawFd;
use std::sync::mpsc::{channel, Receiver, TryIter};
use runloop::RunLoop;
use util::to_io_err;
const UDEV_SUBSYSTEM: &'static str = "hidraw";
const POLLIN: c_short = 0x0001;
const POLL_TIMEOUT: c_int = 100;
fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> {
let nfds = fds.len() as c_ulong;
let rv = unsafe { ::libc::poll((&mut fds[..]).as_mut_ptr(), nfds, POLL_TIMEOUT) };
if rv < 0 {
Err(io::Error::from_raw_os_error(rv))
} else {
Ok(())
}
}
pub enum Event {
Add(OsString),
Remove(OsString),
}
impl Event {
fn from_udev(event: libudev::Event) -> Option<Self> {
let path = event.device().devnode().map(
|dn| dn.to_owned().into_os_string(),
);
match (event.event_type(), path) {
(EventType::Add, Some(path)) => Some(Event::Add(path)),
(EventType::Remove, Some(path)) => Some(Event::Remove(path)),
_ => None,
}
}
}
pub struct Monitor {
// Receive events from the thread.
rx: Receiver<Event>,
// Handle to the thread loop.
thread: RunLoop,
}
impl Monitor {
pub fn new() -> io::Result<Self> {
let (tx, rx) = channel();
let thread = RunLoop::new(
move |alive| -> io::Result<()> {
let ctx = libudev::Context::new()?;
let mut enumerator = libudev::Enumerator::new(&ctx)?;
enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
// Iterate all existing devices.
for dev in enumerator.scan_devices()? {
if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) {
tx.send(Event::Add(path)).map_err(to_io_err)?;
}
}
let mut monitor = libudev::Monitor::new(&ctx)?;
monitor.match_subsystem(UDEV_SUBSYSTEM)?;
// Start listening for new devices.
let mut socket = monitor.listen()?;
let mut fds = vec![
::libc::pollfd {
fd: socket.as_raw_fd(),
events: POLLIN,
revents: 0,
},
];
// Loop until we're stopped by the controlling thread, or fail.
while alive() {
// Wait for new events, break on failure.
poll(&mut fds)?;
// Send the event over.
let udev_event = socket.receive_event();
if let Some(event) = udev_event.and_then(Event::from_udev) {
tx.send(event).map_err(to_io_err)?;
}
}
Ok(())
},
0, /* no timeout */
)?;
Ok(Self { rx, thread })
}
pub fn events<'a>(&'a self) -> TryIter<'a, Event> {
self.rx.try_iter()
}
pub fn alive(&self) -> bool {
self.thread.alive()
}
}
impl Drop for Monitor {
fn drop(&mut self) {
self.thread.cancel();
}
}

Просмотреть файл

@ -0,0 +1,192 @@
/* 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;
extern crate log;
use std::fmt;
use std::io;
use std::io::{Read, Write};
use std::slice;
use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError};
use std::time::Duration;
use core_foundation_sys::base::*;
use libc::c_void;
use consts::{CID_BROADCAST, HID_RPT_SIZE};
use u2ftypes::U2FDevice;
use super::iokit::*;
const READ_TIMEOUT: u64 = 15;
pub struct Device {
device_ref: IOHIDDeviceRef,
cid: [u8; 4],
report_rx: Receiver<Vec<u8>>,
report_send_void: *mut c_void,
scratch_buf_ptr: *mut u8,
}
impl Device {
pub fn new(device_ref: IOHIDDeviceRef) -> Self {
let (report_tx, report_rx) = channel();
let report_send_void = Box::into_raw(Box::new(report_tx)) as *mut c_void;
let scratch_buf = [0; HID_RPT_SIZE];
let scratch_buf_ptr = Box::into_raw(Box::new(scratch_buf)) as *mut u8;
unsafe {
IOHIDDeviceRegisterInputReportCallback(
device_ref,
scratch_buf_ptr,
HID_RPT_SIZE as CFIndex,
read_new_data_cb,
report_send_void,
);
}
Self {
device_ref,
cid: CID_BROADCAST,
report_rx,
report_send_void,
scratch_buf_ptr,
}
}
}
impl Drop for Device {
fn drop(&mut self) {
debug!("Dropping U2F device {}", self);
unsafe {
// Re-allocate raw pointers for destruction.
let _ = Box::from_raw(self.report_send_void as *mut Sender<Vec<u8>>);
let _ = Box::from_raw(self.scratch_buf_ptr as *mut [u8; HID_RPT_SIZE]);
}
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"InternalDevice(ref:{:?}, cid: {:02x}{:02x}{:02x}{:02x})",
self.device_ref,
self.cid[0],
self.cid[1],
self.cid[2],
self.cid[3]
)
}
}
impl PartialEq for Device {
fn eq(&self, other_device: &Device) -> bool {
self.device_ref == other_device.device_ref
}
}
impl Read for Device {
fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> {
let timeout = Duration::from_secs(READ_TIMEOUT);
let data = match self.report_rx.recv_timeout(timeout) {
Ok(v) => v,
Err(e) if e == RecvTimeoutError::Timeout => {
return Err(io::Error::new(io::ErrorKind::TimedOut, e));
}
Err(e) => {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e));
}
};
bytes.write(&data)
}
}
impl Write for Device {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
assert_eq!(bytes.len(), HID_RPT_SIZE + 1);
let report_id = bytes[0] as i64;
// Skip report number when not using numbered reports.
let start = if report_id == 0x0 { 1 } else { 0 };
let data = &bytes[start..];
let result = unsafe {
IOHIDDeviceSetReport(
self.device_ref,
kIOHIDReportTypeOutput,
report_id,
data.as_ptr(),
data.len() as CFIndex,
)
};
if result != 0 {
warn!("set_report sending failure = {0:X}", result);
return Err(io::Error::from_raw_os_error(result));
}
trace!("set_report sending success = {0:X}", result);
Ok(bytes.len())
}
// 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(&self) -> &[u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
}
// This is called from the RunLoop thread
extern "C" fn read_new_data_cb(
context: *mut c_void,
_: IOReturn,
_: *mut c_void,
report_type: IOHIDReportType,
report_id: u32,
report: *mut u8,
report_len: CFIndex,
) {
unsafe {
let tx = &mut *(context as *mut Sender<Vec<u8>>);
trace!(
"read_new_data_cb type={} id={} report={:?} len={}",
report_type,
report_id,
report,
report_len
);
let report_len = report_len as usize;
if report_len > HID_RPT_SIZE {
warn!(
"read_new_data_cb got too much data! {} > {}",
report_len,
HID_RPT_SIZE
);
return;
}
let data = slice::from_raw_parts(report, report_len).to_vec();
if let Err(e) = tx.send(data) {
// TOOD: This happens when the channel closes before this thread
// does. This is pretty common, but let's deal with stopping
// properly later.
warn!("Problem returning read_new_data_cb data for thread: {}", e);
};
}
}

Просмотреть файл

@ -0,0 +1,51 @@
/* 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 std::collections::hash_map::ValuesMut;
use std::collections::HashMap;
use u2fprotocol::u2f_init_device;
use platform::monitor::Event;
use platform::device::Device;
use platform::iokit::*;
pub struct DeviceMap {
map: HashMap<IOHIDDeviceRef, Device>,
}
impl DeviceMap {
pub fn new() -> Self {
Self { map: HashMap::new() }
}
pub fn values_mut(&mut self) -> ValuesMut<IOHIDDeviceRef, Device> {
self.map.values_mut()
}
pub fn process_event(&mut self, event: Event) {
match event {
Event::Add(dev) => self.add(dev),
Event::Remove(dev) => self.remove(dev),
}
}
fn add(&mut self, device_ref: IOHIDDeviceRef) {
if self.map.contains_key(&device_ref) {
return;
}
// Create the device.
let mut dev = Device::new(device_ref);
if u2f_init_device(&mut dev) {
self.map.insert(device_ref, dev);
}
}
fn remove(&mut self, device_ref: IOHIDDeviceRef) {
// Ignore errors.
let _ = self.map.remove(&device_ref);
}
}

Просмотреть файл

@ -0,0 +1,127 @@
/* 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 log;
extern crate libc;
use std::io;
use super::iokit::*;
use core_foundation_sys::base::*;
use core_foundation_sys::dictionary::*;
use core_foundation_sys::number::*;
use core_foundation_sys::runloop::*;
use core_foundation_sys::string::*;
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
use util::io_err;
pub struct IOHIDDeviceMatcher {
dict: CFDictionaryRef,
keys: Vec<CFStringRef>,
values: Vec<CFNumberRef>,
}
impl IOHIDDeviceMatcher {
pub fn new() -> Self {
let keys = vec![
IOHIDDeviceMatcher::cf_string("DeviceUsage"),
IOHIDDeviceMatcher::cf_string("DeviceUsagePage"),
];
let values = vec![
IOHIDDeviceMatcher::cf_number(FIDO_USAGE_U2FHID as i32),
IOHIDDeviceMatcher::cf_number(FIDO_USAGE_PAGE as i32),
];
let dict = unsafe {
CFDictionaryCreate(
kCFAllocatorDefault,
keys.as_ptr() as *const *const libc::c_void,
values.as_ptr() as *const *const libc::c_void,
keys.len() as CFIndex,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
)
};
Self { dict, keys, values }
}
fn cf_number(number: i32) -> CFNumberRef {
let nbox = Box::new(number);
let nptr = Box::into_raw(nbox) as *mut libc::c_void;
unsafe {
// Drop when out of scope.
let _num = Box::from_raw(nptr as *mut i32);
CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, nptr)
}
}
fn cf_string(string: &str) -> CFStringRef {
unsafe {
CFStringCreateWithBytes(
kCFAllocatorDefault,
string.as_ptr(),
string.len() as CFIndex,
kCFStringEncodingUTF8,
false as Boolean,
kCFAllocatorNull,
)
}
}
pub fn get(&self) -> CFDictionaryRef {
self.dict
}
}
impl Drop for IOHIDDeviceMatcher {
fn drop(&mut self) {
unsafe { CFRelease(self.dict as *mut libc::c_void) };
for key in &self.keys {
unsafe { CFRelease(*key as *mut libc::c_void) };
}
for value in &self.values {
unsafe { CFRelease(*value as *mut libc::c_void) };
}
}
}
pub struct IOHIDManager {
manager: IOHIDManagerRef,
}
impl IOHIDManager {
pub fn new() -> io::Result<Self> {
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
let rv = unsafe { IOHIDManagerOpen(manager, kIOHIDManagerOptionNone) };
if rv != 0 {
return Err(io_err("Couldn't open HID Manager"));
}
unsafe {
IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)
};
Ok(Self { manager })
}
pub fn get(&self) -> IOHIDManagerRef {
self.manager
}
}
impl Drop for IOHIDManager {
fn drop(&mut self) {
let rv = unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
if rv != 0 {
warn!("Couldn't close the HID Manager");
}
}
}

Просмотреть файл

@ -0,0 +1,93 @@
/* 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/. */
#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
extern crate core_foundation_sys;
extern crate libc;
use libc::c_void;
use core_foundation_sys::base::{CFIndex, CFAllocatorRef};
use core_foundation_sys::string::CFStringRef;
use core_foundation_sys::runloop::CFRunLoopRef;
use core_foundation_sys::dictionary::CFDictionaryRef;
type IOOptionBits = u32;
pub type IOReturn = libc::c_int;
pub type IOHIDManagerRef = *mut __IOHIDManager;
pub type IOHIDManagerOptions = IOOptionBits;
pub type IOHIDDeviceCallback = extern "C" fn(context: *mut c_void,
result: IOReturn,
sender: *mut c_void,
device: IOHIDDeviceRef);
pub type IOHIDReportType = IOOptionBits;
pub type IOHIDReportCallback = extern "C" fn(context: *mut c_void,
result: IOReturn,
sender: *mut c_void,
report_type: IOHIDReportType,
report_id: u32,
report: *mut u8,
report_len: CFIndex);
pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0;
pub const kIOHIDReportTypeOutput: IOHIDReportType = 1;
#[repr(C)]
pub struct __IOHIDManager {
__private: c_void,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct IOHIDDeviceRef(*const c_void);
unsafe impl Send for IOHIDDeviceRef {}
unsafe impl Sync for IOHIDDeviceRef {}
extern "C" {
// IOHIDManager
pub fn IOHIDManagerCreate(
allocator: CFAllocatorRef,
options: IOHIDManagerOptions,
) -> IOHIDManagerRef;
pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
pub fn IOHIDManagerRegisterDeviceMatchingCallback(
manager: IOHIDManagerRef,
callback: IOHIDDeviceCallback,
context: *mut c_void,
);
pub fn IOHIDManagerRegisterDeviceRemovalCallback(
manager: IOHIDManagerRef,
callback: IOHIDDeviceCallback,
context: *mut c_void,
);
pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
pub fn IOHIDManagerScheduleWithRunLoop(
manager: IOHIDManagerRef,
runLoop: CFRunLoopRef,
runLoopMode: CFStringRef,
);
// IOHIDDevice
pub fn IOHIDDeviceSetReport(
device: IOHIDDeviceRef,
reportType: IOHIDReportType,
reportID: CFIndex,
report: *const u8,
reportLength: CFIndex,
) -> IOReturn;
pub fn IOHIDDeviceRegisterInputReportCallback(
device: IOHIDDeviceRef,
report: *const u8,
reportLength: CFIndex,
callback: IOHIDReportCallback,
context: *mut c_void,
);
}

Просмотреть файл

@ -0,0 +1,183 @@
/* 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 log;
extern crate libc;
use std::thread;
use std::time::Duration;
mod device;
mod devicemap;
mod iokit;
mod iohid;
mod monitor;
use self::devicemap::DeviceMap;
use self::monitor::Monitor;
use consts::PARAMETER_SIZE;
use runloop::RunLoop;
use util::{io_err, OnceCallback};
use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid};
#[derive(Default)]
pub struct PlatformManager {
// Handle to the thread loop.
thread: Option<RunLoop>,
}
impl PlatformManager {
pub fn new() -> Self {
Default::default()
}
pub fn register(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
callback: OnceCallback<Vec<u8>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let thread = RunLoop::new(
move |alive| {
let mut devices = DeviceMap::new();
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
'top: while alive() && monitor.alive() {
for event in monitor.events() {
devices.process_event(event);
}
for device in devices.values_mut() {
// Caller asked us to register, so the first token that does wins
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
callback.call(Ok(bytes));
return;
}
// Check to see if monitor.events has any hotplug events that we'll need
// to handle
if monitor.events().size_hint().0 > 0 {
debug!("Hotplug event; restarting loop");
continue 'top;
}
}
thread::sleep(Duration::from_millis(100));
}
callback.call(Err(io_err("aborted or timed out")));
},
timeout,
);
self.thread = Some(try_or!(
thread,
|_| cbc.call(Err(io_err("couldn't create runloop")))
));
}
pub fn sign(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let thread = RunLoop::new(
move |alive| {
let mut devices = DeviceMap::new();
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
'top: while alive() && monitor.alive() {
for event in monitor.events() {
devices.process_event(event);
}
for key_handle in &key_handles {
for device in devices.values_mut() {
// Determine if this key handle belongs to this token
let is_valid = match u2f_is_keyhandle_valid(
device,
&challenge,
&application,
key_handle,
) {
Ok(result) => result,
Err(_) => continue, // Skip this device for now.
};
if is_valid {
// It does, we can sign
if let Ok(bytes) = u2f_sign(
device,
&challenge,
&application,
key_handle,
)
{
callback.call(Ok((key_handle.clone(), bytes)));
return;
}
} else {
// If doesn't, so blink anyway (using bogus data)
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(device, &blank, &blank).is_ok() {
// If the user selects this token that can't satisfy, it's an
// error
callback.call(Err(io_err("invalid key")));
return;
}
}
// Check to see if monitor.events has any hotplug events that we'll
// need to handle
if monitor.events().size_hint().0 > 0 {
debug!("Hotplug event; restarting loop");
continue 'top;
}
}
}
thread::sleep(Duration::from_millis(100));
}
callback.call(Err(io_err("aborted or timed out")));
},
timeout,
);
self.thread = Some(try_or!(
thread,
|_| cbc.call(Err(io_err("couldn't create runloop")))
));
}
pub fn cancel(&mut self) {
if let Some(thread) = self.thread.take() {
thread.cancel();
}
}
}
impl Drop for PlatformManager {
fn drop(&mut self) {
debug!("OSX PlatformManager dropped");
self.cancel();
}
}

Просмотреть файл

@ -0,0 +1,119 @@
/* 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 std::io;
use std::sync::mpsc::{channel, Sender, Receiver, TryIter};
use std::thread;
use super::iohid::*;
use super::iokit::*;
use core_foundation_sys::runloop::*;
use runloop::RunLoop;
extern crate log;
extern crate libc;
use libc::c_void;
pub enum Event {
Add(IOHIDDeviceRef),
Remove(IOHIDDeviceRef),
}
pub struct Monitor {
// Receive events from the thread.
rx: Receiver<Event>,
// Handle to the thread loop.
thread: RunLoop,
}
impl Monitor {
pub fn new() -> io::Result<Self> {
let (tx, rx) = channel();
let thread = RunLoop::new(
move |alive| -> io::Result<()> {
let tx_box = Box::new(tx);
let tx_ptr = Box::into_raw(tx_box) as *mut libc::c_void;
// This will keep `tx` alive only for the scope.
let _tx = unsafe { Box::from_raw(tx_ptr as *mut Sender<Event>) };
// Create and initialize a scoped HID manager.
let manager = IOHIDManager::new()?;
// Match only U2F devices.
let dict = IOHIDDeviceMatcher::new();
unsafe { IOHIDManagerSetDeviceMatching(manager.get(), dict.get()) };
// Register callbacks.
unsafe {
IOHIDManagerRegisterDeviceMatchingCallback(
manager.get(),
Monitor::device_add_cb,
tx_ptr,
);
IOHIDManagerRegisterDeviceRemovalCallback(
manager.get(),
Monitor::device_remove_cb,
tx_ptr,
);
}
// Run the Event Loop. CFRunLoopRunInMode() will dispatch HID
// input reports into the various callbacks
while alive() {
trace!("OSX Runloop running, handle={:?}", thread::current());
if unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, 0) } ==
kCFRunLoopRunStopped
{
debug!("OSX Runloop device stopped.");
break;
}
}
debug!("OSX Runloop completed, handle={:?}", thread::current());
Ok(())
},
0, /* no timeout */
)?;
Ok(Self { rx, thread })
}
pub fn events(&self) -> TryIter<Event> {
self.rx.try_iter()
}
pub fn alive(&self) -> bool {
self.thread.alive()
}
extern "C" fn device_add_cb(
context: *mut c_void,
_: IOReturn,
_: *mut c_void,
device: IOHIDDeviceRef,
) {
let tx = unsafe { &*(context as *mut Sender<Event>) };
let _ = tx.send(Event::Add(device));
}
extern "C" fn device_remove_cb(
context: *mut c_void,
_: IOReturn,
_: *mut c_void,
device: IOHIDDeviceRef,
) {
let tx = unsafe { &*(context as *mut Sender<Event>) };
let _ = tx.send(Event::Remove(device));
}
}
impl Drop for Monitor {
fn drop(&mut self) {
debug!("OSX Runloop dropped");
self.thread.cancel();
}
}

Просмотреть файл

@ -0,0 +1,173 @@
/* 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 std::io;
use std::sync::mpsc::{channel, Sender, RecvTimeoutError};
use std::time::Duration;
use consts::PARAMETER_SIZE;
use platform::PlatformManager;
use runloop::RunLoop;
use util::{to_io_err, OnceCallback};
pub enum QueueAction {
Register {
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
callback: OnceCallback<Vec<u8>>,
},
Sign {
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
},
Cancel,
}
pub struct U2FManager {
queue: RunLoop,
tx: Sender<QueueAction>,
}
impl U2FManager {
pub fn new() -> io::Result<Self> {
let (tx, rx) = channel();
// Start a new work queue thread.
let queue = try!(RunLoop::new(
move |alive| {
let mut pm = PlatformManager::new();
while alive() {
match rx.recv_timeout(Duration::from_millis(50)) {
Ok(QueueAction::Register {
timeout,
challenge,
application,
callback,
}) => {
// This must not block, otherwise we can't cancel.
pm.register(timeout, challenge, application, callback);
}
Ok(QueueAction::Sign {
timeout,
challenge,
application,
key_handles,
callback,
}) => {
// This must not block, otherwise we can't cancel.
pm.sign(timeout, challenge, application, key_handles, callback);
}
Ok(QueueAction::Cancel) => {
// Cancelling must block so that we don't start a new
// polling thread before the old one has shut down.
pm.cancel();
}
Err(RecvTimeoutError::Disconnected) => {
break;
}
_ => { /* continue */ }
}
}
// Cancel any ongoing activity.
pm.cancel();
},
0, /* no timeout */
));
Ok(Self {
queue: queue,
tx: tx,
})
}
pub fn register<F>(
&self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
callback: F,
) -> io::Result<()>
where
F: FnOnce(io::Result<Vec<u8>>),
F: Send + 'static,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid parameter sizes",
));
}
let callback = OnceCallback::new(callback);
let action = QueueAction::Register {
timeout: timeout,
challenge: challenge,
application: application,
callback: callback,
};
self.tx.send(action).map_err(to_io_err)
}
pub fn sign<F>(
&self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
callback: F,
) -> io::Result<()>
where
F: FnOnce(io::Result<(Vec<u8>, Vec<u8>)>),
F: Send + 'static,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid parameter sizes",
));
}
if key_handles.len() < 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"No key handles given",
));
}
for key_handle in &key_handles {
if key_handle.len() > 256 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Key handle too large",
));
}
}
let callback = OnceCallback::new(callback);
let action = QueueAction::Sign {
timeout: timeout,
challenge: challenge,
application: application,
key_handles: key_handles,
callback: callback,
};
self.tx.send(action).map_err(to_io_err)
}
pub fn cancel(&self) -> io::Result<()> {
self.tx.send(QueueAction::Cancel).map_err(to_io_err)
}
}
impl Drop for U2FManager {
fn drop(&mut self) {
self.queue.cancel();
}
}

Просмотреть файл

@ -0,0 +1,146 @@
/* 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 std::io;
use std::sync::{Arc, Mutex, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
struct Canary {
alive: AtomicBool,
thread: Mutex<Option<JoinHandle<()>>>,
}
impl Canary {
fn new() -> Self {
Self {
alive: AtomicBool::new(true),
thread: Mutex::new(None),
}
}
}
pub struct RunLoop {
flag: Weak<Canary>,
}
impl RunLoop {
pub fn new<F, T>(fun: F, timeout_ms: u64) -> io::Result<Self>
where
F: FnOnce(&Fn() -> bool) -> T,
F: Send + 'static,
{
let flag = Arc::new(Canary::new());
let flag_ = flag.clone();
// Spawn the run loop thread.
let thread = thread::Builder::new().spawn(move || {
let timeout = Duration::from_millis(timeout_ms);
let start = Instant::now();
// A callback to determine whether the thread should terminate.
let still_alive = || {
// `flag.alive` will be false after cancel() was called.
flag.alive.load(Ordering::Relaxed) &&
// If a timeout was provided, we'll check that too.
(timeout_ms == 0 || start.elapsed() < timeout)
};
// Ignore return values.
let _ = fun(&still_alive);
})?;
// We really should never fail to lock here.
let mut guard = (*flag_).thread.lock().map_err(|_| {
io::Error::new(io::ErrorKind::Other, "failed to lock")
})?;
// Store the thread handle so we can join later.
*guard = Some(thread);
Ok(Self { flag: Arc::downgrade(&flag_) })
}
// Cancels the run loop and waits for the thread to terminate.
// This is a potentially BLOCKING operation.
pub fn cancel(&self) {
// If the thread still exists...
if let Some(flag) = self.flag.upgrade() {
// ...let the run loop terminate.
flag.alive.store(false, Ordering::Relaxed);
// Locking should never fail here either.
if let Ok(mut guard) = flag.thread.lock() {
// This really can't fail.
if let Some(handle) = (*guard).take() {
// This might fail, ignore.
let _ = handle.join();
}
}
}
}
// Tells whether the runloop is alive.
pub fn alive(&self) -> bool {
// If the thread still exists...
if let Some(flag) = self.flag.upgrade() {
flag.alive.load(Ordering::Relaxed)
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Barrier};
use super::RunLoop;
#[test]
fn test_empty() {
// Create a runloop that exits right away.
let thread = RunLoop::new(move |_| {}, 0).unwrap();
while thread.alive() { /* wait */ }
thread.cancel(); // noop
}
#[test]
fn test_cancel_early() {
// Create a runloop and cancel it before the thread spawns.
RunLoop::new(|alive| assert!(!alive()), 0).unwrap().cancel();
}
#[test]
fn test_cancel_endless_loop() {
let barrier = Arc::new(Barrier::new(2));
let b = barrier.clone();
// Create a runloop that never exits.
let thread = RunLoop::new(
move |alive| {
b.wait();
while alive() { /* loop */ }
},
0,
).unwrap();
barrier.wait();
assert!(thread.alive());
thread.cancel();
assert!(!thread.alive());
}
#[test]
fn test_timeout() {
// Create a runloop that never exits, but times out after a second.
let thread = RunLoop::new(|alive| while alive() {}, 1).unwrap();
while thread.alive() { /* wait */ }
assert!(!thread.alive());
thread.cancel(); // noop
}
}

Просмотреть файл

@ -0,0 +1,80 @@
/* 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/. */
#ifndef __U2FHID_CAPI
#define __U2FHID_CAPI
#include <stdlib.h>
#include "nsString.h"
extern "C" {
const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
// NOTE: Preconditions
// * All rust_u2f_mgr* pointers must refer to pointers which are returned
// by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
// * All rust_u2f_khs* pointers must refer to pointers which are returned
// by rust_u2f_khs_new, and must be freed with rust_u2f_khs_free.
// * All rust_u2f_res* pointers must refer to pointers passed to the
// register() and sign() callbacks. They can be null on failure.
// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
struct rust_u2f_manager;
// The `rust_u2f_key_handles` opaque type is equivalent to the rust type `U2FKeyHandles`
struct rust_u2f_key_handles;
// The `rust_u2f_res` opaque type is equivalent to the rust type `U2FResult`
struct rust_u2f_result;
// The callback passed to register() and sign().
typedef void (*rust_u2f_callback)(uint64_t, rust_u2f_result*);
/// U2FManager functions.
rust_u2f_manager* rust_u2f_mgr_new();
/* unsafe */ void rust_u2f_mgr_free(rust_u2f_manager* mgr);
uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr,
uint64_t timeout,
rust_u2f_callback,
const uint8_t* challenge_ptr,
size_t challenge_len,
const uint8_t* application_ptr,
size_t application_len);
uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
uint64_t timeout,
rust_u2f_callback,
const uint8_t* challenge_ptr,
size_t challenge_len,
const uint8_t* application_ptr,
size_t application_len,
const rust_u2f_key_handles* khs);
uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
/// U2FKeyHandles functions.
rust_u2f_key_handles* rust_u2f_khs_new();
void rust_u2f_khs_add(rust_u2f_key_handles* khs,
const uint8_t* key_handle,
size_t key_handle_len);
/* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);
/// U2FResult functions.
// Call this before `[..]_copy()` to allocate enough space.
bool rust_u2f_resbuf_length(const rust_u2f_result *res, uint8_t bid, size_t* len);
bool rust_u2f_resbuf_copy(const rust_u2f_result *res, uint8_t bid, uint8_t* dst);
/* unsafe */ void rust_u2f_res_free(rust_u2f_result* res);
}
#endif // __U2FHID_CAPI

Просмотреть файл

@ -0,0 +1,441 @@
/* 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 std;
use rand::{thread_rng, Rng};
use std::io;
use std::io::{Read, Write};
use std::ffi::CString;
use consts::*;
use u2ftypes::*;
use util::io_err;
////////////////////////////////////////////////////////////////////////
// Device Commands
////////////////////////////////////////////////////////////////////////
pub fn u2f_init_device<T>(dev: &mut T) -> bool
where
T: U2FDevice + Read + Write,
{
// Do a few U2F device checks.
let mut nonce = [0u8; 8];
thread_rng().fill_bytes(&mut nonce);
if init_device(dev, &nonce).is_err() {
return false;
}
let mut random = [0u8; 8];
thread_rng().fill_bytes(&mut random);
if ping_device(dev, &random).is_err() {
return false;
}
is_v2_device(dev).unwrap_or(false)
}
pub fn u2f_register<T>(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result<Vec<u8>>
where
T: U2FDevice + Read + Write,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid parameter sizes",
));
}
let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE);
register_data.extend(challenge);
register_data.extend(application);
let flags = U2F_REQUEST_USER_PRESENCE;
let (resp, status) = send_apdu(dev, U2F_REGISTER, flags, &register_data)?;
status_word_to_result(status, resp)
}
pub fn u2f_sign<T>(
dev: &mut T,
challenge: &[u8],
application: &[u8],
key_handle: &[u8],
) -> io::Result<Vec<u8>>
where
T: U2FDevice + Read + Write,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid parameter sizes",
));
}
if key_handle.len() > 256 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Key handle too large",
));
}
let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len());
sign_data.extend(challenge);
sign_data.extend(application);
sign_data.push(key_handle.len() as u8);
sign_data.extend(key_handle);
let flags = U2F_REQUEST_USER_PRESENCE;
let (resp, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?;
status_word_to_result(status, resp)
}
pub fn u2f_is_keyhandle_valid<T>(
dev: &mut T,
challenge: &[u8],
application: &[u8],
key_handle: &[u8],
) -> io::Result<bool>
where
T: U2FDevice + Read + Write,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid parameter sizes",
));
}
if key_handle.len() > 256 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Key handle too large",
));
}
let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len());
sign_data.extend(challenge);
sign_data.extend(application);
sign_data.push(key_handle.len() as u8);
sign_data.extend(key_handle);
let flags = U2F_CHECK_IS_REGISTERED;
let (_, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?;
Ok(status == SW_CONDITIONS_NOT_SATISFIED)
}
////////////////////////////////////////////////////////////////////////
// Internal Device Commands
////////////////////////////////////////////////////////////////////////
fn init_device<T>(dev: &mut T, nonce: &[u8]) -> io::Result<()>
where
T: U2FDevice + Read + Write,
{
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
let raw = sendrecv(dev, U2FHID_INIT, nonce)?;
dev.set_cid(U2FHIDInitResp::read(&raw, nonce)?);
Ok(())
}
fn ping_device<T>(dev: &mut T, random: &[u8]) -> io::Result<()>
where
T: U2FDevice + Read + Write,
{
assert_eq!(random.len(), 8);
if sendrecv(dev, U2FHID_PING, random)? != random {
return Err(io_err("Ping was corrupted!"));
}
Ok(())
}
fn is_v2_device<T>(dev: &mut T) -> io::Result<bool>
where
T: U2FDevice + Read + Write,
{
let (data, status) = send_apdu(dev, U2F_VERSION, 0x00, &[])?;
let actual = CString::new(data)?;
let expected = CString::new("U2F_V2")?;
status_word_to_result(status, actual == expected)
}
////////////////////////////////////////////////////////////////////////
// Error Handling
////////////////////////////////////////////////////////////////////////
fn status_word_to_result<T>(status: [u8; 2], val: T) -> io::Result<T> {
use self::io::ErrorKind::{InvalidData, InvalidInput};
match status {
SW_NO_ERROR => Ok(val),
SW_WRONG_DATA => Err(io::Error::new(InvalidData, "wrong data")),
SW_WRONG_LENGTH => Err(io::Error::new(InvalidInput, "wrong length")),
SW_CONDITIONS_NOT_SATISFIED => Err(io_err("conditions not satisfied")),
_ => Err(io_err(&format!("failed with status {:?}", status))),
}
}
////////////////////////////////////////////////////////////////////////
// Device Communication Functions
////////////////////////////////////////////////////////////////////////
pub fn sendrecv<T>(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result<Vec<u8>>
where
T: U2FDevice + Read + Write,
{
// Send initialization packet.
let mut count = U2FHIDInit::write(dev, cmd, send)?;
// Send continuation packets.
let mut sequence = 0u8;
while count < send.len() {
count += U2FHIDCont::write(dev, sequence, &send[count..])?;
sequence += 1;
}
// Now we read. This happens in 2 chunks: The initial packet, which has the
// size we expect overall, then continuation packets, which will fill in
// data until we have everything.
let mut data = U2FHIDInit::read(dev)?;
let mut sequence = 0u8;
while data.len() < data.capacity() {
let max = data.capacity() - data.len();
data.extend_from_slice(&U2FHIDCont::read(dev, sequence, max)?);
sequence += 1;
}
Ok(data)
}
fn send_apdu<T>(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])>
where
T: U2FDevice + Read + Write,
{
let apdu = U2FAPDUHeader::to_bytes(cmd, p1, send)?;
let mut data = sendrecv(dev, U2FHID_MSG, &apdu)?;
if data.len() < 2 {
return Err(io_err("unexpected response"));
}
let split_at = data.len() - 2;
let status = data.split_off(split_at);
Ok((data, [status[0], status[1]]))
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use rand::{thread_rng, Rng};
use super::{U2FDevice, init_device, ping_device, sendrecv, send_apdu};
use consts::{CID_BROADCAST, U2FHID_INIT, U2FHID_PING, U2FHID_MSG, SW_NO_ERROR};
mod platform {
use std::io;
use std::io::{Read, Write};
use consts::{CID_BROADCAST, HID_RPT_SIZE};
use u2ftypes::U2FDevice;
pub struct TestDevice {
cid: [u8; 4],
reads: Vec<[u8; HID_RPT_SIZE]>,
writes: Vec<[u8; HID_RPT_SIZE + 1]>,
}
impl TestDevice {
pub fn new() -> TestDevice {
TestDevice {
cid: CID_BROADCAST,
reads: vec![],
writes: vec![],
}
}
pub fn add_write(&mut self, packet: &[u8], fill_value: u8) {
// Add one to deal with record index check
let mut write = [fill_value; HID_RPT_SIZE + 1];
// Make sure we start with a 0, for HID record index
write[0] = 0;
// Clone packet data in at 1, since front is padded with HID record index
write[1..packet.len() + 1].clone_from_slice(packet);
self.writes.push(write);
}
pub fn add_read(&mut self, packet: &[u8], fill_value: u8) {
let mut read = [fill_value; HID_RPT_SIZE];
read[..packet.len()].clone_from_slice(packet);
self.reads.push(read);
}
}
impl Write for TestDevice {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
// Pop a vector from the expected writes, check for quality
// against bytes array.
assert!(self.writes.len() > 0, "Ran out of expected write values!");
let check = self.writes.remove(0);
assert_eq!(check.len(), bytes.len());
assert_eq!(&check[..], bytes);
Ok(bytes.len())
}
// nop
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Read for TestDevice {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
assert!(self.reads.len() > 0, "Ran out of read values!");
let check = self.reads.remove(0);
assert_eq!(check.len(), bytes.len());
bytes.clone_from_slice(&check[..]);
Ok(check.len())
}
}
impl Drop for TestDevice {
fn drop(&mut self) {
assert_eq!(self.reads.len(), 0);
assert_eq!(self.writes.len(), 0);
}
}
impl U2FDevice for TestDevice {
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
}
}
#[test]
fn test_init_device() {
let mut device = platform::TestDevice::new();
let nonce = vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
// channel id
let mut cid = [0u8; 4];
thread_rng().fill_bytes(&mut cid);
// init packet
let mut msg = CID_BROADCAST.to_vec();
msg.extend(vec![U2FHID_INIT, 0x00, 0x08]); // cmd + bcnt
msg.extend_from_slice(&nonce);
device.add_write(&msg, 0);
// init_resp packet
let mut msg = CID_BROADCAST.to_vec();
msg.extend(vec![U2FHID_INIT, 0x00, 0x11]); // cmd + bcnt
msg.extend_from_slice(&nonce);
msg.extend_from_slice(&cid); // new channel id
msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags
device.add_read(&msg, 0);
init_device(&mut device, &nonce).unwrap();
assert_eq!(device.get_cid(), &cid);
}
#[test]
fn test_sendrecv_multiple() {
let mut device = platform::TestDevice::new();
let cid = [0x01, 0x02, 0x03, 0x04];
device.set_cid(cid.clone());
// init packet
let mut msg = cid.to_vec();
msg.extend(vec![U2FHID_PING, 0x00, 0xe4]); // cmd + length = 228
// write msg, append [1u8; 57], 171 bytes remaining
device.add_write(&msg, 1);
device.add_read(&msg, 1);
// cont packet
let mut msg = cid.to_vec();
msg.push(0x00); // seq = 0
// write msg, append [1u8; 59], 112 bytes remaining
device.add_write(&msg, 1);
device.add_read(&msg, 1);
// cont packet
let mut msg = cid.to_vec();
msg.push(0x01); // seq = 1
// write msg, append [1u8; 59], 53 bytes remaining
device.add_write(&msg, 1);
device.add_read(&msg, 1);
// cont packet
let mut msg = cid.to_vec();
msg.push(0x02); // seq = 2
msg.extend_from_slice(&[1u8; 53]);
// write msg, append remaining 53 bytes.
device.add_write(&msg, 0);
device.add_read(&msg, 0);
let data = [1u8; 228];
let d = sendrecv(&mut device, U2FHID_PING, &data).unwrap();
assert_eq!(d.len(), 228);
assert_eq!(d, &data[..]);
}
#[test]
fn test_sendapdu() {
let cid = [0x01, 0x02, 0x03, 0x04];
let data = [0x01, 0x02, 0x03, 0x04, 0x05];
let mut device = platform::TestDevice::new();
device.set_cid(cid.clone());
let mut msg = cid.to_vec();
// sendrecv header
msg.extend(vec![U2FHID_MSG, 0x00, 0x0e]); // len = 14
// apdu header
msg.extend(vec![0x00, U2FHID_PING, 0xaa, 0x00, 0x00, 0x00, 0x05]);
// apdu data
msg.extend_from_slice(&data);
device.add_write(&msg, 0);
// Send data back
let mut msg = cid.to_vec();
msg.extend(vec![U2FHID_MSG, 0x00, 0x07]);
msg.extend_from_slice(&data);
msg.extend_from_slice(&SW_NO_ERROR);
device.add_read(&msg, 0);
let (result, status) = send_apdu(&mut device, U2FHID_PING, 0xaa, &data).unwrap();
assert_eq!(result, &data);
assert_eq!(status, SW_NO_ERROR);
}
#[test]
fn test_ping_device() {
let mut device = platform::TestDevice::new();
device.set_cid([0x01, 0x02, 0x03, 0x04]);
// ping nonce
let random = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
// APDU header
let mut msg = vec![0x01, 0x02, 0x03, 0x04, U2FHID_PING, 0x00, 0x08];
msg.extend_from_slice(&random);
device.add_write(&msg, 0);
// Only expect data from APDU back
let mut msg = vec![0x01, 0x02, 0x03, 0x04, U2FHID_MSG, 0x00, 0x08];
msg.extend_from_slice(&random);
device.add_read(&msg, 0);
ping_device(&mut device, &random).unwrap();
}
}

Просмотреть файл

@ -0,0 +1,188 @@
/* 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 std::{cmp, io};
use consts::*;
use util::io_err;
use log;
fn trace_hex(data: &[u8]) {
if log_enabled!(log::LogLevel::Trace) {
let parts: Vec<String> = data.iter().map(|byte| format!("{:02x}", byte)).collect();
trace!("USB send: {}", parts.join(""));
}
}
// Trait for representing U2F HID Devices. Requires getters/setters for the
// channel ID, created during device initialization.
pub trait U2FDevice {
fn get_cid(&self) -> &[u8; 4];
fn set_cid(&mut self, cid: [u8; 4]);
}
// Init structure for U2F Communications. Tells the receiver what channel
// communication is happening on, what command is running, and how much data to
// expect to receive over all.
//
// Spec at https://fidoalliance.org/specs/fido-u2f-v1.
// 0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.html#message--and-packet-structure
pub struct U2FHIDInit {}
impl U2FHIDInit {
pub fn read<T>(dev: &mut T) -> io::Result<Vec<u8>>
where
T: U2FDevice + io::Read,
{
let mut frame = [0u8; HID_RPT_SIZE];
let count = dev.read(&mut frame)?;
if count != HID_RPT_SIZE {
return Err(io_err("invalid init packet"));
}
if dev.get_cid() != &frame[..4] {
return Err(io_err("invalid channel id"));
}
let cap = (frame[5] as usize) << 8 | (frame[6] as usize);
let mut data = Vec::with_capacity(cap);
let len = cmp::min(cap, INIT_DATA_SIZE);
data.extend_from_slice(&frame[7..7 + len]);
Ok(data)
}
pub fn write<T>(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result<usize>
where
T: U2FDevice + io::Write,
{
if data.len() > 0xffff {
return Err(io_err("payload length > 2^16"));
}
let mut frame = [0; HID_RPT_SIZE + 1];
frame[1..5].copy_from_slice(dev.get_cid());
frame[5] = cmd;
frame[6] = (data.len() >> 8) as u8;
frame[7] = data.len() as u8;
let count = cmp::min(data.len(), INIT_DATA_SIZE);
frame[8..8 + count].copy_from_slice(&data[..count]);
trace_hex(&frame);
if dev.write(&frame)? != frame.len() {
return Err(io_err("device write failed"));
}
Ok(count)
}
}
// Continuation structure for U2F Communications. After an Init structure is
// sent, continuation structures are used to transmit all extra data that
// wouldn't fit in the initial packet. The sequence number increases with every
// packet, until all data is received.
//
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
// html#message--and-packet-structure
pub struct U2FHIDCont {}
impl U2FHIDCont {
pub fn read<T>(dev: &mut T, seq: u8, max: usize) -> io::Result<Vec<u8>>
where
T: U2FDevice + io::Read,
{
let mut frame = [0u8; HID_RPT_SIZE];
let count = dev.read(&mut frame)?;
if count != HID_RPT_SIZE {
return Err(io_err("invalid cont packet"));
}
if dev.get_cid() != &frame[..4] {
return Err(io_err("invalid channel id"));
}
if seq != frame[4] {
return Err(io_err("invalid sequence number"));
}
let max = cmp::min(max, CONT_DATA_SIZE);
Ok(frame[5..5 + max].to_vec())
}
pub fn write<T>(dev: &mut T, seq: u8, data: &[u8]) -> io::Result<usize>
where
T: U2FDevice + io::Write,
{
let mut frame = [0; HID_RPT_SIZE + 1];
frame[1..5].copy_from_slice(dev.get_cid());
frame[5] = seq;
let count = cmp::min(data.len(), CONT_DATA_SIZE);
frame[6..6 + count].copy_from_slice(&data[..count]);
trace_hex(&frame);
if dev.write(&frame)? != frame.len() {
return Err(io_err("device write failed"));
}
Ok(count)
}
}
// Reply sent after initialization command. Contains information about U2F USB
// Key versioning, as well as the communication channel to be used for all
// further requests.
//
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
// html#u2fhid_init
pub struct U2FHIDInitResp {}
impl U2FHIDInitResp {
pub fn read(data: &[u8], nonce: &[u8]) -> io::Result<[u8; 4]> {
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
if data.len() != INIT_NONCE_SIZE + 9 {
return Err(io_err("invalid init response"));
}
if nonce != &data[..INIT_NONCE_SIZE] {
return Err(io_err("invalid nonce"));
}
let mut cid = [0u8; 4];
cid.copy_from_slice(&data[INIT_NONCE_SIZE..INIT_NONCE_SIZE + 4]);
Ok(cid)
}
}
// https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit
// https://fidoalliance.org/specs/fido-u2f-v1.
// 0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#u2f-message-framing
pub struct U2FAPDUHeader {}
impl U2FAPDUHeader {
pub fn to_bytes(ins: u8, p1: u8, data: &[u8]) -> io::Result<Vec<u8>> {
if data.len() > 0xffff {
return Err(io_err("payload length > 2^16"));
}
// Size of header + data + 2 zero bytes for maximum return size.
let mut bytes = vec![0u8; U2FAPDUHEADER_SIZE + data.len() + 2];
bytes[1] = ins;
bytes[2] = p1;
// p2 is always 0, at least, for our requirements.
// lc[0] should always be 0.
bytes[5] = (data.len() >> 8) as u8;
bytes[6] = data.len() as u8;
bytes[7..7 + data.len()].copy_from_slice(data);
Ok(bytes)
}
}

Просмотреть файл

@ -0,0 +1,83 @@
/* 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::error::Error;
use std::io;
use std::sync::{Arc, Mutex};
use boxfnonce::SendBoxFnOnce;
macro_rules! try_or {
($val:expr, $or:expr) => {
match $val {
Ok(v) => { v }
Err(e) => { return $or(e); }
}
}
}
pub trait Signed {
fn is_negative(&self) -> bool;
}
impl Signed for i32 {
fn is_negative(&self) -> bool {
*self < (0 as i32)
}
}
impl Signed for usize {
fn is_negative(&self) -> bool {
(*self as isize) < (0 as isize)
}
}
#[cfg(any(target_os = "linux"))]
pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
if rv.is_negative() {
let errno = unsafe { *libc::__errno_location() };
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)
}
pub fn to_io_err<T: Error>(err: T) -> io::Error {
io_err(err.description())
}
pub struct OnceCallback<T> {
callback: Arc<Mutex<Option<SendBoxFnOnce<(io::Result<T>,)>>>>,
}
impl<T> OnceCallback<T> {
pub fn new<F>(cb: F) -> Self
where
F: FnOnce(io::Result<T>),
F: Send + 'static,
{
let cb = Some(SendBoxFnOnce::from(cb));
Self { callback: Arc::new(Mutex::new(cb)) }
}
pub fn call(&self, rv: io::Result<T>) {
if let Ok(mut cb) = self.callback.lock() {
if let Some(cb) = cb.take() {
cb.call(rv);
}
}
}
}
impl<T> Clone for OnceCallback<T> {
fn clone(&self) -> Self {
Self { callback: self.callback.clone() }
}
}

Просмотреть файл

@ -0,0 +1,74 @@
/* 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 std::fs::{File, OpenOptions};
use std::io;
use std::io::{Read, Write};
use std::os::windows::io::AsRawHandle;
use consts::{CID_BROADCAST, HID_RPT_SIZE, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
use super::winapi::DeviceCapabilities;
use u2ftypes::U2FDevice;
#[derive(Debug)]
pub struct Device {
path: String,
file: File,
cid: [u8; 4],
}
impl Device {
pub fn new(path: String) -> io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(&path)?;
Ok(Self {
path: path,
file: file,
cid: CID_BROADCAST,
})
}
pub fn is_u2f(&self) -> bool {
match DeviceCapabilities::new(self.file.as_raw_handle()) {
Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE,
_ => false,
}
}
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.path == other.path
}
}
impl Read for Device {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
// Windows always includes the report ID.
let mut input = [0u8; HID_RPT_SIZE + 1];
let _ = self.file.read(&mut input)?;
bytes.clone_from_slice(&input[1..]);
Ok(bytes.len() as usize)
}
}
impl Write for Device {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.file.write(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
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,49 @@
/* 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 std::collections::hash_map::ValuesMut;
use std::collections::HashMap;
use platform::device::Device;
use platform::monitor::Event;
use u2fprotocol::u2f_init_device;
pub struct DeviceMap {
map: HashMap<String, Device>,
}
impl DeviceMap {
pub fn new() -> Self {
Self { map: HashMap::new() }
}
pub fn values_mut(&mut self) -> ValuesMut<String, Device> {
self.map.values_mut()
}
pub fn process_event(&mut self, event: Event) {
match event {
Event::Add(path) => self.add(path),
Event::Remove(path) => self.remove(path),
}
}
fn add(&mut self, path: String) {
if self.map.contains_key(&path) {
return;
}
// Create and try to open the device.
if let Ok(mut dev) = Device::new(path.clone()) {
if dev.is_u2f() && u2f_init_device(&mut dev) {
self.map.insert(path, dev);
}
}
}
fn remove(&mut self, path: String) {
// Ignore errors.
let _ = self.map.remove(&path);
}
}

Просмотреть файл

@ -0,0 +1,157 @@
/* 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 std::thread;
use std::time::Duration;
mod device;
mod devicemap;
mod monitor;
mod winapi;
use consts::PARAMETER_SIZE;
use runloop::RunLoop;
use util::{io_err, OnceCallback};
use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid};
use self::devicemap::DeviceMap;
use self::monitor::Monitor;
pub struct PlatformManager {
// Handle to the thread loop.
thread: Option<RunLoop>,
}
impl PlatformManager {
pub fn new() -> Self {
Self { thread: None }
}
pub fn register(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
callback: OnceCallback<Vec<u8>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let thread = RunLoop::new(
move |alive| {
let mut devices = DeviceMap::new();
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
while alive() && monitor.alive() {
// Add/remove devices.
for event in monitor.events() {
devices.process_event(event);
}
// Try to register each device.
for device in devices.values_mut() {
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
callback.call(Ok(bytes));
return;
}
}
// Wait a little before trying again.
thread::sleep(Duration::from_millis(100));
}
callback.call(Err(io_err("aborted or timed out")));
},
timeout,
);
self.thread = Some(try_or!(thread, |_| {
cbc.call(Err(io_err("couldn't create runloop")));
}));
}
pub fn sign(
&mut self,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let thread = RunLoop::new(
move |alive| {
let mut devices = DeviceMap::new();
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
while alive() && monitor.alive() {
// Add/remove devices.
for event in monitor.events() {
devices.process_event(event);
}
// Try signing with each device.
for key_handle in &key_handles {
for device in devices.values_mut() {
// Check if they key handle belongs to the current device.
let is_valid = match u2f_is_keyhandle_valid(
device,
&challenge,
&application,
&key_handle,
) {
Ok(valid) => valid,
Err(_) => continue, // Skip this device for now.
};
if is_valid {
// If yes, try to sign.
if let Ok(bytes) = u2f_sign(
device,
&challenge,
&application,
&key_handle,
)
{
callback.call(Ok((key_handle.clone(), bytes)));
return;
}
} else {
// If no, keep registering and blinking with bogus data
let blank = vec![0u8; PARAMETER_SIZE];
if let Ok(_) = u2f_register(device, &blank, &blank) {
callback.call(Err(io_err("invalid key")));
return;
}
}
}
}
// Wait a little before trying again.
thread::sleep(Duration::from_millis(100));
}
callback.call(Err(io_err("aborted or timed out")));
},
timeout,
);
self.thread = Some(try_or!(thread, |_| {
cbc.call(Err(io_err("couldn't create runloop")));
}));
}
// This might block.
pub fn cancel(&mut self) {
if let Some(thread) = self.thread.take() {
thread.cancel();
}
}
}

Просмотреть файл

@ -0,0 +1,89 @@
/* 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 std::collections::HashSet;
use std::error::Error;
use std::io;
use std::iter::FromIterator;
use std::sync::mpsc::{channel, Receiver, TryIter};
use std::thread;
use std::time::Duration;
use runloop::RunLoop;
use super::winapi::DeviceInfoSet;
pub fn io_err(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::Other, msg)
}
pub fn to_io_err<T: Error>(err: T) -> io::Error {
io_err(err.description())
}
pub enum Event {
Add(String),
Remove(String),
}
pub struct Monitor {
// Receive events from the thread.
rx: Receiver<Event>,
// Handle to the thread loop.
thread: RunLoop,
}
impl Monitor {
pub fn new() -> io::Result<Self> {
let (tx, rx) = channel();
let thread = RunLoop::new(
move |alive| -> io::Result<()> {
let mut stored = HashSet::new();
while alive() {
let device_info_set = DeviceInfoSet::new()?;
let devices = HashSet::from_iter(device_info_set.devices());
// Remove devices that are gone.
for path in stored.difference(&devices) {
tx.send(Event::Remove(path.clone())).map_err(to_io_err)?;
}
// Add devices that were plugged in.
for path in devices.difference(&stored) {
tx.send(Event::Add(path.clone())).map_err(to_io_err)?;
}
// Remember the new set.
stored = devices;
// Wait a little before looking for devices again.
thread::sleep(Duration::from_millis(100));
}
Ok(())
},
0, /* no timeout */
)?;
Ok(Self {
rx: rx,
thread: thread,
})
}
pub fn events<'a>(&'a self) -> TryIter<'a, Event> {
self.rx.try_iter()
}
pub fn alive(&self) -> bool {
self.thread.alive()
}
}
impl Drop for Monitor {
fn drop(&mut self) {
self.thread.cancel();
}
}

Просмотреть файл

@ -0,0 +1,263 @@
/* 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 std::io;
use std::mem;
use std::ptr;
use std::slice;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use util::io_err;
extern crate libc;
extern crate winapi;
use self::winapi::*;
#[link(name = "setupapi")]
extern "stdcall" {
fn SetupDiGetClassDevsW(
ClassGuid: *const GUID,
Enumerator: PCSTR,
hwndParent: HWND,
flags: DWORD,
) -> HDEVINFO;
fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: HDEVINFO) -> BOOL;
fn SetupDiEnumDeviceInterfaces(
DeviceInfoSet: HDEVINFO,
DeviceInfoData: PSP_DEVINFO_DATA,
InterfaceClassGuid: *const GUID,
MemberIndex: DWORD,
DeviceInterfaceData: PSP_DEVICE_INTERFACE_DATA,
) -> BOOL;
fn SetupDiGetDeviceInterfaceDetailW(
DeviceInfoSet: HDEVINFO,
DeviceInterfaceData: PSP_DEVICE_INTERFACE_DATA,
DeviceInterfaceDetailData: PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
DeviceInterfaceDetailDataSize: DWORD,
RequiredSize: PDWORD,
DeviceInfoData: PSP_DEVINFO_DATA,
) -> BOOL;
}
#[link(name = "hid")]
extern "stdcall" {
fn HidD_GetPreparsedData(
HidDeviceObject: HANDLE,
PreparsedData: *mut PHIDP_PREPARSED_DATA,
) -> BOOLEAN;
fn HidD_FreePreparsedData(PreparsedData: PHIDP_PREPARSED_DATA) -> BOOLEAN;
fn HidP_GetCaps(PreparsedData: PHIDP_PREPARSED_DATA, Capabilities: PHIDP_CAPS) -> NTSTATUS;
}
macro_rules! offset_of {
($ty:ty, $field:ident) => {
unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
}
}
fn from_wide_ptr(ptr: *const u16, len: usize) -> String {
assert!(!ptr.is_null() && len % 2 == 0);
let slice = unsafe { slice::from_raw_parts(ptr, len / 2) };
OsString::from_wide(slice).to_string_lossy().into_owned()
}
pub struct DeviceInfoSet {
set: HDEVINFO,
}
impl DeviceInfoSet {
pub fn new() -> io::Result<Self> {
let flags = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE;
let set = unsafe {
SetupDiGetClassDevsW(
&GUID_DEVINTERFACE_HID,
ptr::null_mut(),
ptr::null_mut(),
flags,
)
};
if set == INVALID_HANDLE_VALUE {
return Err(io_err("SetupDiGetClassDevsW failed!"));
}
Ok(Self { set })
}
pub fn get(&self) -> HDEVINFO {
self.set
}
pub fn devices(&self) -> DeviceInfoSetIter {
DeviceInfoSetIter::new(self)
}
}
impl Drop for DeviceInfoSet {
fn drop(&mut self) {
let _ = unsafe { SetupDiDestroyDeviceInfoList(self.set) };
}
}
pub struct DeviceInfoSetIter<'a> {
set: &'a DeviceInfoSet,
index: DWORD,
}
impl<'a> DeviceInfoSetIter<'a> {
fn new(set: &'a DeviceInfoSet) -> Self {
Self { set, index: 0 }
}
}
impl<'a> Iterator for DeviceInfoSetIter<'a> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
let mut device_interface_data = unsafe { mem::uninitialized::<SP_DEVICE_INTERFACE_DATA>() };
device_interface_data.cbSize = mem::size_of::<SP_DEVICE_INTERFACE_DATA>() as UINT;
let rv = unsafe {
SetupDiEnumDeviceInterfaces(
self.set.get(),
ptr::null_mut(),
&GUID_DEVINTERFACE_HID,
self.index,
&mut device_interface_data,
)
};
if rv == 0 {
return None; // We're past the last device index.
}
// Determine the size required to hold a detail struct.
let mut required_size = 0;
unsafe {
SetupDiGetDeviceInterfaceDetailW(
self.set.get(),
&mut device_interface_data,
ptr::null_mut(),
required_size,
&mut required_size,
ptr::null_mut(),
)
};
if required_size == 0 {
return None; // An error occurred.
}
let detail = DeviceInterfaceDetailData::new(required_size as usize);
if detail.is_none() {
return None; // malloc() failed.
}
let detail = detail.unwrap();
let rv = unsafe {
SetupDiGetDeviceInterfaceDetailW(
self.set.get(),
&mut device_interface_data,
detail.get(),
required_size,
ptr::null_mut(),
ptr::null_mut(),
)
};
if rv == 0 {
return None; // An error occurred.
}
self.index += 1;
Some(detail.path())
}
}
struct DeviceInterfaceDetailData {
data: PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
path_len: usize,
}
impl DeviceInterfaceDetailData {
fn new(size: usize) -> Option<Self> {
let mut cb_size = mem::size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>();
if cfg!(target_pointer_width = "32") {
cb_size = 4 + 2; // 4-byte uint + default TCHAR size. size_of is inaccurate.
}
if size < cb_size {
warn!("DeviceInterfaceDetailData is too small. {}", size);
return None;
}
let mut data = unsafe { libc::malloc(size) as PSP_DEVICE_INTERFACE_DETAIL_DATA_W };
if data.is_null() {
return None;
}
// Set total size of the structure.
unsafe { (*data).cbSize = cb_size as UINT };
// Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`.
let offset = offset_of!(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
Some(Self {
data,
path_len: size - offset,
})
}
fn get(&self) -> PSP_DEVICE_INTERFACE_DETAIL_DATA_W {
self.data
}
fn path(&self) -> String {
unsafe { from_wide_ptr((*self.data).DevicePath.as_ptr(), self.path_len - 2) }
}
}
impl Drop for DeviceInterfaceDetailData {
fn drop(&mut self) {
unsafe { libc::free(self.data as *mut libc::c_void) };
}
}
pub struct DeviceCapabilities {
caps: HIDP_CAPS,
}
impl DeviceCapabilities {
pub fn new(handle: HANDLE) -> io::Result<Self> {
let mut preparsed_data = ptr::null_mut();
let rv = unsafe { HidD_GetPreparsedData(handle, &mut preparsed_data) };
if rv == 0 || preparsed_data.is_null() {
return Err(io_err("HidD_GetPreparsedData failed!"));
}
let mut caps: HIDP_CAPS = unsafe { mem::uninitialized() };
unsafe {
let rv = HidP_GetCaps(preparsed_data, &mut caps);
HidD_FreePreparsedData(preparsed_data);
if rv != HIDP_STATUS_SUCCESS {
return Err(io_err("HidP_GetCaps failed!"));
}
}
Ok(Self { caps })
}
pub fn usage(&self) -> USAGE {
self.caps.Usage
}
pub fn usage_page(&self) -> USAGE {
self.caps.UsagePage
}
}