Bug 1388843 - Part 1: Copy u2f-hid-rs into dom/webauthn/ r=gerv,qdot
authorTim Taubert <ttaubert@mozilla.com>
Wed, 09 Aug 2017 21:16:49 +0200
changeset 380905 435c255e8718c7e565e6ae0376ba0456832ef190
parent 380904 e217f827cc16e3ec1373b97c37e9345f9d4da256
child 380906 66ca504303b87c614b3413fc7bf6abc3e6278dde
push id95010
push userttaubert@mozilla.com
push dateThu, 14 Sep 2017 18:10:28 +0000
treeherdermozilla-inbound@7c0cba1eef91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerv, qdot
bugs1388843
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
+    }
+}