Bug 1388843 - Part 1: Copy u2f-hid-rs into dom/webauthn/ r=gerv,qdot draft
authorTim Taubert <ttaubert@mozilla.com>
Wed, 09 Aug 2017 21:16:49 +0200
changeset 665175 12f6f7ab39462e95b8b7ccefc00268692ab9329f
parent 665088 8e818b5e9b6bef0fc1a5c527ecf30b0d56a02f14
child 665176 c30d0cb1c5841c748dcaef93a770bd3565cb054a
push id79949
push userbmo:jbeich@FreeBSD.org
push dateFri, 15 Sep 2017 00:31:58 +0000
reviewersgerv, qdot
bugs1388843
milestone57.0a1
Bug 1388843 - Part 1: Copy u2f-hid-rs into dom/webauthn/ r=gerv,qdot
dom/webauthn/u2f-hid-rs/.gitignore
dom/webauthn/u2f-hid-rs/.travis.yml
dom/webauthn/u2f-hid-rs/Cargo.toml
dom/webauthn/u2f-hid-rs/LICENSE
dom/webauthn/u2f-hid-rs/README.md
dom/webauthn/u2f-hid-rs/build.rs
dom/webauthn/u2f-hid-rs/examples/main.rs
dom/webauthn/u2f-hid-rs/fuzz/.gitignore
dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/1172fb09a3c99ed54c7d677faae5386d938d5834
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/3e28ec02ceb803efa105dc82fcfc9cb4014d7ced
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5232969ad85258a14c3fbdb7b3e112539755f3a4
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5814556fab57a61da7d4150c2b45c15686df25ec
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/60c1a897bef5145eb977461d13f789e1ddfa4a69
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/7f960b178bcf3adf082da7c632b4e1366ba22274
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a22d2b1daa9483abc026fa2f75290aa51be59ca9
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a5142fc55a5d72c9978bd54c73c8e32743bd8cfe
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/ab4368e52f60dbe956d28100adf2a4d110b615ec
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/1172fb09a3c99ed54c7d677faae5386d938d5834
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/62b74bc0ad2433f77a007b95eaf964ea719d21e2
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/680d38f668ab4372aaf0d57910c30a5ce6a6a03d
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/f94c8e14f32ca38e773745c5622e2da2a18c3a47
dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read.rs
dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read_write.rs
dom/webauthn/u2f-hid-rs/rustfmt.toml
dom/webauthn/u2f-hid-rs/src/android/mod.rs
dom/webauthn/u2f-hid-rs/src/capi.rs
dom/webauthn/u2f-hid-rs/src/consts.rs
dom/webauthn/u2f-hid-rs/src/lib.rs
dom/webauthn/u2f-hid-rs/src/linux/device.rs
dom/webauthn/u2f-hid-rs/src/linux/devicemap.rs
dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs
dom/webauthn/u2f-hid-rs/src/linux/mod.rs
dom/webauthn/u2f-hid-rs/src/linux/monitor.rs
dom/webauthn/u2f-hid-rs/src/macos/device.rs
dom/webauthn/u2f-hid-rs/src/macos/devicemap.rs
dom/webauthn/u2f-hid-rs/src/macos/iohid.rs
dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
dom/webauthn/u2f-hid-rs/src/macos/mod.rs
dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
dom/webauthn/u2f-hid-rs/src/manager.rs
dom/webauthn/u2f-hid-rs/src/runloop.rs
dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs
dom/webauthn/u2f-hid-rs/src/u2ftypes.rs
dom/webauthn/u2f-hid-rs/src/util.rs
dom/webauthn/u2f-hid-rs/src/windows/device.rs
dom/webauthn/u2f-hid-rs/src/windows/devicemap.rs
dom/webauthn/u2f-hid-rs/src/windows/mod.rs
dom/webauthn/u2f-hid-rs/src/windows/monitor.rs
dom/webauthn/u2f-hid-rs/src/windows/winapi.rs
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/.gitignore
@@ -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
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/.travis.yml
@@ -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
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/Cargo.toml
@@ -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"
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/LICENSE
@@ -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.
+
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/README.md
@@ -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
+```
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/build.rs
@@ -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");
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/examples/main.rs
@@ -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.");
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/fuzz/.gitignore
@@ -0,0 +1,2 @@
+target
+artifacts
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml
@@ -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"
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ea3c72d74777dd0e45ab394d76bd6ddf15a0331
GIT binary patch
literal 64
Tc${O<XV}!tz?9j_Kpp@9iQEEJ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cfaf37c1a656f666fa294abe13638d0cd1e21feb
GIT binary patch
literal 64
jc%1wH|Ns9s2EnX-k~?lQ&YQ`g&#;M!g^7)kfk*%VAU6q+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cb8d8b2cbdd9158ae43ec1c56f839ae25ada0347
GIT binary patch
literal 140
bc%1wH9|9N<;5!2*z$V7<A5$-OK%_zdlX4l?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..730031931ce97bbb16d2a9212e32bdf894784a27
GIT binary patch
literal 151
fc%1wH9|9PFfRTY1un+<M;{(`r<CkS%V1iHp-|REO
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3655d50ce7e463cb5785cd3bb0bee699f572fdb7
GIT binary patch
literal 512
zc${NkKmz}Ppp8K=YoFwf+l=#OGUzjGVq#%pV`Lx_s1pLHb|Kk{4scpQPytTB1p*Z4
UC&wAsgOT)r#Q;p$97K+K06q6g+5i9m
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cc0fd3ff81cfa5a77ad67ddfd9cb3b296686408c
GIT binary patch
literal 512
zc%1wH9|9O~0S;_}TsV~z3AjLjR80+d9f4vZ7j`vBR-?E8Ng2gn!wZP?Hy19K;L}Z(
F%K_{G3F!a;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..57883a2b45edc9aaaefe2f1431d92b74b19a3a5e
GIT binary patch
literal 226
dc%1wH|NnmmV!?l8Kv)F^Akt(a^$|7|3;^5?RIC61
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..01cf945a13d09697d562c229cc620643d1400c17
GIT binary patch
literal 110
Rc%1wH9|9OC1OJIL1^{e25Ags1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4971b88b4de19beadfe5f865eec085565e23f08d
GIT binary patch
literal 105
ac%1wH|NnmmC}3eg2mdhvPBj=3r~m+(rXJ=1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ea3c72d74777dd0e45ab394d76bd6ddf15a0331
GIT binary patch
literal 64
Tc${O<XV}!tz?9j_Kpp@9iQEEJ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e1f9493e6dc1681e137e3e5be835c51187566fb6
GIT binary patch
literal 512
zc${O<XV}cp%n{0<&#<YPfhn_<0XslfX0+99;R2%xzhUy|`WPV;6E>6ZT0|oNbU!Xz
Tsp%X-!9;>NM7fGGTYvxnf<Yvd
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d15aceb08e6239987939f4b90c37be1e4e8240da
GIT binary patch
literal 33
jc${O<XV}cp%n{0<&#<YPfhn`qXsg@81x6EoGe7|VlWqt?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..67d346bd9b4ab681761bd5b2e209034d9dff75d6
GIT binary patch
literal 128
hc${O<XV~1#z!7TX79VCbfq`@Y*3XpLN`_Vt002h{1_uBD
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read.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);
+});
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read_write.rs
@@ -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()[..]);
+});
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/rustfmt.toml
@@ -0,0 +1,2 @@
+comment_width = 200
+wrap_comments = true
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/android/mod.rs
@@ -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
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/capi.rs
@@ -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()
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/consts.rs
@@ -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];
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/lib.rs
@@ -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::*;
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/linux/device.rs
@@ -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;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/linux/devicemap.rs
@@ -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);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs
@@ -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
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/linux/mod.rs
@@ -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();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/linux/monitor.rs
@@ -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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/device.rs
@@ -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);
+        };
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/devicemap.rs
@@ -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);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/iohid.rs
@@ -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");
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
@@ -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,
+    );
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/mod.rs
@@ -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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
@@ -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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/manager.rs
@@ -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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/runloop.rs
@@ -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
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
@@ -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
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs
@@ -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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/u2ftypes.rs
@@ -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)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/util.rs
@@ -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() }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/windows/device.rs
@@ -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;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/windows/devicemap.rs
@@ -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);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/windows/mod.rs
@@ -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();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/windows/monitor.rs
@@ -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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/windows/winapi.rs
@@ -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
+    }
+}