Bug 1520166 - Part 1: Use a vendored version of authenticator. r=jcj

This replaces the in-tree u2fhid (which has been renamed to
authenticator) by the published crate.

Differential Revision: https://phabricator.services.mozilla.com/D32221

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Bastien Orivel 2019-05-24 07:31:26 +00:00
parent 90d25c0076
commit d24b02b94c
62 changed files with 26 additions and 4600 deletions

51
Cargo.lock generated
View File

@ -140,6 +140,23 @@ dependencies = [
"tokio-uds 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "authenticator"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"boxfnonce 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"devd-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
]
[[package]]
name = "autocfg"
version = "0.1.2"
@ -889,11 +906,11 @@ dependencies = [
[[package]]
name = "devd-rs"
version = "0.2.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1255,6 +1272,7 @@ dependencies = [
"arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"audioipc-client 0.4.0",
"audioipc-server 0.2.3",
"authenticator 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"bitsdownload 0.1.0",
"bookmark_sync 0.1.0",
"cert_storage 0.0.1",
@ -1280,7 +1298,6 @@ dependencies = [
"rsdparsa_capi 0.1.0",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"storage 0.1.0",
"u2fhid 0.2.4",
"webrender_bindings 0.1.0",
"xpcom 0.1.0",
"xulstore 0.1.0",
@ -1977,14 +1994,6 @@ name = "nodrop"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nom"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nom"
version = "4.1.1"
@ -3297,22 +3306,6 @@ name = "typenum"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "u2fhid"
version = "0.2.4"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"boxfnonce 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"devd-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
]
[[package]]
name = "ucd-util"
version = "0.1.1"
@ -3727,6 +3720,7 @@ dependencies = [
"checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2"
"checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum authenticator 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ec149e5d5d4caa2c9ead53a8ce1ea9c4204c388c65bf3b96c2d1dc0fcf4aeb66"
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
@ -3796,7 +3790,7 @@ dependencies = [
"checksum darling_macro 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "244e8987bd4e174385240cde20a3657f607fb0797563c28255c353b5819a07b1"
"checksum deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8a6abb26e16e8d419b5c78662aa9f82857c2386a073da266840e474d5055ec86"
"checksum derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f57d78cf3bd45270dad4e70c21ec77a960b36c7a841ff9db76aaa775a8fb871"
"checksum devd-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c9ac481c38baf400d3b732e4a06850dfaa491d1b6379a249d9d40d14c2434c"
"checksum devd-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d009f166c0d9e9f9909dc751630b3a6411ab7f85a153d32d01deb364ffe52a7"
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
"checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
@ -3888,7 +3882,6 @@ dependencies = [
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b"
"checksum nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a"
"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2"
"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"

View File

@ -8,7 +8,7 @@
#define mozilla_dom_U2FHIDTokenManager_h
#include "mozilla/dom/U2FTokenTransport.h"
#include "u2f-hid-rs/src/u2fhid-capi.h"
#include "authenticator/src/u2fhid-capi.h"
/*
* U2FHIDTokenManager is a Rust implementation of a secure token manager

View File

@ -61,6 +61,7 @@ LOCAL_INCLUDES += [
'/dom/base',
'/dom/crypto',
'/security/manager/ssl',
'/third_party/rust',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':

View File

@ -1 +0,0 @@
type-complexity-threshold = 384

View File

@ -1,13 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
**/*.rs.bk
# Fuzzing corpuses should be explicitly updated
fuzz/corpus/
# 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

View File

@ -1,40 +0,0 @@
sudo: false
language: rust
cache: cargo
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
addons:
apt:
packages:
- build-essential
- libudev-dev
before_install:
- pkg-config --list-all
- pkg-config --libs libudev
- pkg-config --modversion libudev
install:
- rustup install nightly
- rustup component add rustfmt-preview
- rustup component add clippy-preview
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
- |
if [ "$TRAVIS_RUST_VERSION" == "stable" ] ; then
cargo fmt --all -- --check
cargo clippy --all-targets -- -A renamed_and_removed_lints -A new-ret-no-self -D warnings
fi
- cargo test

View File

@ -1,36 +0,0 @@
[package]
name = "u2fhid"
version = "0.2.4"
authors = ["Kyle Machulis <kyle@nonpolynomial.com>", "J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>"]
[target.'cfg(target_os = "linux")'.dependencies]
libudev = "^0.2"
[target.'cfg(target_os = "freebsd")'.dependencies]
devd-rs = "0.2.1"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.6.2"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3"
features = [
"handleapi",
"hidclass",
"hidpi",
"hidusage",
"setupapi",
]
[dependencies]
rand = "0.6"
log = "0.4"
libc = "^0.2"
boxfnonce = "0.0.3"
runloop = "0.1.0"
bitflags = "1.0"
[dev-dependencies]
sha2 = "^0.7"
base64 = "^0.4"
env_logger = "0.5"

View File

@ -1,374 +0,0 @@
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.

View File

@ -1,50 +0,0 @@
# 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, FreeBSD, and macOS.
* **Supported HID Transports**: USB.
* **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html).
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 --example main
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/
cargo +nightly fuzz run u2f_read -- -max_len=512
cargo +nightly fuzz run u2f_read_write -- -max_len=512
```

View File

@ -1,107 +0,0 @@
/* 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 sha2;
extern crate u2fhid;
use sha2::{Digest, Sha256};
use std::io;
use std::sync::mpsc::channel;
use u2fhid::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, U2FManager};
extern crate env_logger;
extern crate log;
macro_rules! try_or {
($val:expr, $or:expr) => {
match $val {
Ok(v) => v,
Err(e) => {
return $or(e);
}
}
};
}
fn u2f_get_key_handle_from_register_response(register_response: &[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.to_owned();
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();
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::default();
challenge.input(challenge_str.as_bytes());
let chall_bytes = challenge.result().to_vec();
let mut application = Sha256::default();
application.input(b"http://demo.yubico.com");
let app_bytes = application.result().to_vec();
let manager = U2FManager::new().unwrap();
let flags = RegisterFlags::empty();
let (tx, rx) = channel();
manager
.register(
flags,
15_000,
chall_bytes.clone(),
app_bytes.clone(),
vec![],
move |rv| {
tx.send(rv.unwrap()).unwrap();
},
).unwrap();
let register_data = try_or!(rx.recv(), |_| {
panic!("Problem receiving, unable to continue");
});
println!("Register result: {}", base64::encode(&register_data));
println!("Asking a security key to sign now, with the data from the register...");
let credential = u2f_get_key_handle_from_register_response(&register_data).unwrap();
let key_handle = KeyHandle {
credential,
transports: AuthenticatorTransports::empty(),
};
let flags = SignFlags::empty();
let (tx, rx) = channel();
manager
.sign(
flags,
15_000,
chall_bytes,
vec![app_bytes],
vec![key_handle],
move |rv| {
tx.send(rv.unwrap()).unwrap();
},
).unwrap();
let (_, handle_used, sign_data) = try_or!(rx.recv(), |_| {
println!("Problem receiving");
});
println!("Sign result: {}", base64::encode(&sign_data));
println!("Key handle used: {}", base64::encode(&handle_used));
println!("Done.");
}

View File

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

View File

@ -1,29 +0,0 @@
[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"

View File

@ -1,66 +0,0 @@
/* 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 u2fhid;
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]| {
if data.len() > 0 {
let cmd = data[0];
let data = &data[1..];
let mut dev = TestDevice::new(data);
let _ = sendrecv(&mut dev, cmd, data);
}
});

View File

@ -1,68 +0,0 @@
/* 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 u2fhid;
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]| {
if data.len() > 0 {
let cmd = data[0];
let data = &data[1..];
let mut dev = TestDevice::new();
let res = sendrecv(&mut dev, cmd, data);
assert_eq!(data, &res.unwrap()[..]);
}
});

View File

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

View File

@ -1,270 +0,0 @@
/* 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 U2FAppIds = Vec<::AppId>;
type U2FKeyHandles = Vec<::KeyHandle>;
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
pub enum U2FResult {
Success(HashMap<u8, Vec<u8>>),
Error(::Error),
}
const RESBUF_ID_REGISTRATION: u8 = 0;
const RESBUF_ID_KEYHANDLE: u8 = 1;
const RESBUF_ID_SIGNATURE: u8 = 2;
const RESBUF_ID_APPID: u8 = 3;
// 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_app_ids_new() -> *mut U2FAppIds {
Box::into_raw(Box::new(vec![]))
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_app_ids_add(
ids: *mut U2FAppIds,
id_ptr: *const u8,
id_len: usize,
) {
(*ids).push(from_raw(id_ptr, id_len));
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) {
if !ids.is_null() {
Box::from_raw(ids);
}
}
#[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,
transports: u8,
) {
(*khs).push(::KeyHandle {
credential: from_raw(key_handle_ptr, key_handle_len),
transports: ::AuthenticatorTransports::from_bits_truncate(transports),
});
}
#[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_result_error(res: *const U2FResult) -> u8 {
if res.is_null() {
return ::Error::Unknown as u8;
}
if let U2FResult::Error(ref err) = *res {
return *err as u8;
}
0 /* No error, the request succeeded. */
}
#[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 U2FResult::Success(ref bufs) = *res {
if let Some(buf) = bufs.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 U2FResult::Success(ref bufs) = *res {
if let Some(buf) = bufs.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,
flags: u64,
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() {
return 0;
}
// Check buffers.
if challenge_ptr.is_null() || application_ptr.is_null() {
return 0;
}
let flags = ::RegisterFlags::from_bits_truncate(flags);
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).register(
flags,
timeout,
challenge,
application,
key_handles,
move |rv| {
let result = match rv {
Ok(registration) => {
let mut bufs = HashMap::new();
bufs.insert(RESBUF_ID_REGISTRATION, registration);
U2FResult::Success(bufs)
}
Err(e) => U2FResult::Error(e),
};
callback(tid, Box::into_raw(Box::new(result)));
},
);
if res.is_ok() {
tid
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_sign(
mgr: *mut U2FManager,
flags: u64,
timeout: u64,
callback: U2FCallback,
challenge_ptr: *const u8,
challenge_len: usize,
app_ids: *const U2FAppIds,
khs: *const U2FKeyHandles,
) -> u64 {
if mgr.is_null() || khs.is_null() {
return 0;
}
// Check buffers.
if challenge_ptr.is_null() {
return 0;
}
// Need at least one app_id.
if (*app_ids).is_empty() {
return 0;
}
let flags = ::SignFlags::from_bits_truncate(flags);
let challenge = from_raw(challenge_ptr, challenge_len);
let app_ids = (*app_ids).clone();
let key_handles = (*khs).clone();
let tid = new_tid();
let res = (*mgr).sign(flags, timeout, challenge, app_ids, key_handles, move |rv| {
let result = match rv {
Ok((app_id, key_handle, signature)) => {
let mut bufs = HashMap::new();
bufs.insert(RESBUF_ID_KEYHANDLE, key_handle);
bufs.insert(RESBUF_ID_SIGNATURE, signature);
bufs.insert(RESBUF_ID_APPID, app_id);
U2FResult::Success(bufs)
}
Err(e) => U2FResult::Error(e),
};
callback(tid, Box::into_raw(Box::new(result)));
});
if res.is_ok() {
tid
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut U2FManager) {
if !mgr.is_null() {
// Ignore return value.
let _ = (*mgr).cancel();
}
}

View File

@ -1,78 +0,0 @@
/* 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];

View File

@ -1,88 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate libc;
use std::ffi::{CString, OsString};
use std::io;
use std::io::{Read, Write};
use std::os::unix::prelude::*;
use consts::CID_BROADCAST;
use platform::uhid;
use u2ftypes::U2FDevice;
use util::from_unix_result;
#[derive(Debug)]
pub struct Device {
path: OsString,
fd: libc::c_int,
cid: [u8; 4],
}
impl Device {
pub fn new(path: OsString) -> io::Result<Self> {
let cstr = CString::new(path.as_bytes())?;
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
let fd = from_unix_result(fd)?;
Ok(Self {
path,
fd,
cid: CID_BROADCAST,
})
}
pub fn is_u2f(&self) -> bool {
uhid::is_u2f_device(self.fd)
}
}
impl Drop for Device {
fn drop(&mut self) {
// Close the fd, ignore any errors.
let _ = unsafe { libc::close(self.fd) };
}
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.path == other.path
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
from_unix_result(rv as usize)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let report_id = buf[0] as i64;
// Skip report number when not using numbered reports.
let start = if report_id == 0x0 { 1 } else { 0 };
let data = &buf[start..];
let data_ptr = data.as_ptr() as *const libc::c_void;
let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) };
from_unix_result(rv as usize + 1)
}
// USB HID writes don't buffer, so this will be a nop.
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl U2FDevice for Device {
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
}

View File

@ -1,9 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod device;
pub mod transaction;
mod monitor;
mod uhid;

View File

@ -1,141 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use devd_rs;
use std::collections::HashMap;
use std::ffi::OsString;
use std::sync::Arc;
use std::{fs, io};
use runloop::RunLoop;
const POLL_TIMEOUT: usize = 100;
pub enum Event {
Add(OsString),
Remove(OsString),
}
impl Event {
fn from_devd(event: devd_rs::Event) -> Option<Self> {
match event {
devd_rs::Event::Attach {
ref dev,
parent: _,
location: _,
}
if dev.starts_with("uhid") =>
{
Some(Event::Add(("/dev/".to_owned() + dev).into()))
}
devd_rs::Event::Detach {
ref dev,
parent: _,
location: _,
}
if dev.starts_with("uhid") =>
{
Some(Event::Remove(("/dev/".to_owned() + dev).into()))
}
_ => None,
}
}
}
fn convert_error(e: devd_rs::Error) -> io::Error {
e.into()
}
pub struct Monitor<F>
where
F: Fn(OsString, &Fn() -> bool) + Sync,
{
runloops: HashMap<OsString, RunLoop>,
new_device_cb: Arc<F>,
}
impl<F> Monitor<F>
where
F: Fn(OsString, &Fn() -> bool) + Send + Sync + 'static,
{
pub fn new(new_device_cb: F) -> Self {
Self {
runloops: HashMap::new(),
new_device_cb: Arc::new(new_device_cb),
}
}
pub fn run(&mut self, alive: &Fn() -> bool) -> io::Result<()> {
let mut ctx = devd_rs::Context::new().map_err(convert_error)?;
// Iterate all existing devices.
for dev in fs::read_dir("/dev")? {
if let Ok(dev) = dev {
let filename_ = dev.file_name();
let filename = filename_.to_str().unwrap_or("");
if filename.starts_with("uhid") {
self.add_device(("/dev/".to_owned() + filename).into());
}
}
}
// Loop until we're stopped by the controlling thread, or fail.
while alive() {
// Wait for new events, break on failure.
match ctx.wait_for_event(POLL_TIMEOUT) {
Err(devd_rs::Error::Timeout) => (),
Err(e) => return Err(e.into()),
Ok(event) => {
if let Some(event) = Event::from_devd(event) {
self.process_event(event);
}
}
}
}
// Remove all tracked devices.
self.remove_all_devices();
Ok(())
}
fn process_event(&mut self, event: Event) {
match event {
Event::Add(path) => {
self.add_device(path);
}
Event::Remove(path) => {
self.remove_device(path);
}
}
}
fn add_device(&mut self, path: OsString) {
let f = self.new_device_cb.clone();
let key = path.clone();
let runloop = RunLoop::new(move |alive| {
if alive() {
f(path, alive);
}
});
if let Ok(runloop) = runloop {
self.runloops.insert(key, runloop);
}
}
fn remove_device(&mut self, path: OsString) {
if let Some(runloop) = self.runloops.remove(&path) {
runloop.cancel();
}
}
fn remove_all_devices(&mut self) {
while !self.runloops.is_empty() {
let path = self.runloops.keys().next().unwrap().clone();
self.remove_device(path);
}
}
}

View File

@ -1,48 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use platform::monitor::Monitor;
use runloop::RunLoop;
use std::ffi::OsString;
use util::OnceCallback;
pub struct Transaction {
// Handle to the thread loop.
thread: Option<RunLoop>,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: OnceCallback<T>,
new_device_cb: F,
) -> Result<Self, ::Error>
where
F: Fn(OsString, &Fn() -> bool) + Sync + Send + 'static,
T: 'static,
{
let thread = RunLoop::new_with_timeout(
move |alive| {
// Create a new device monitor.
let mut monitor = Monitor::new(new_device_cb);
// Start polling for new devices.
try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
// Send an error, if the callback wasn't called already.
callback.call(Err(::Error::NotAllowed));
},
timeout,
).map_err(|_| ::Error::Unknown)?;
Ok(Self {
thread: Some(thread),
})
}
pub fn cancel(&mut self) {
// This must never be None.
self.thread.take().unwrap().cancel();
}
}

View File

@ -1,92 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate libc;
use std::io;
use std::os::unix::io::RawFd;
use std::ptr;
use hidproto::*;
use util::from_unix_result;
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
pub struct GenDescriptor {
ugd_data: *mut u8,
ugd_lang_id: u16,
ugd_maxlen: u16,
ugd_actlen: u16,
ugd_offset: u16,
ugd_config_index: u8,
ugd_string_index: u8,
ugd_iface_index: u8,
ugd_altif_index: u8,
ugd_endpt_index: u8,
ugd_report_index: u8,
reserved: [u8; 16],
}
impl Default for GenDescriptor {
fn default() -> GenDescriptor {
GenDescriptor {
ugd_data: ptr::null_mut(),
ugd_lang_id: 0,
ugd_maxlen: 65535,
ugd_actlen: 0,
ugd_offset: 0,
ugd_config_index: 0,
ugd_string_index: 0,
ugd_iface_index: 0,
ugd_altif_index: 0,
ugd_endpt_index: 0,
ugd_report_index: 0,
reserved: [0; 16],
}
}
}
const IOWR: u32 = 0x40000000 | 0x80000000;
const IOCPARM_SHIFT: u32 = 13;
const IOCPARM_MASK: u32 = ((1 << IOCPARM_SHIFT) - 1);
const TYPESHIFT: u32 = 8;
const SIZESHIFT: u32 = 16;
macro_rules! ioctl {
($dir:expr, $name:ident, $ioty:expr, $nr:expr, $size:expr; $ty:ty) => {
pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
let ioc = ($dir as u32)
| (($size as u32 & IOCPARM_MASK) << SIZESHIFT)
| (($ioty as u32) << TYPESHIFT)
| ($nr as u32);
from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val))
}
};
}
// https://github.com/freebsd/freebsd/blob/master/sys/dev/usb/usb_ioctl.h
ioctl!(IOWR, usb_get_report_desc, b'U', 21, 32; /*struct*/ GenDescriptor);
fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
let mut desc = GenDescriptor::default();
let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
desc.ugd_maxlen = desc.ugd_actlen;
let mut value = Vec::with_capacity(desc.ugd_actlen as usize);
unsafe {
value.set_len(desc.ugd_actlen as usize);
}
desc.ugd_data = value.as_mut_ptr();
let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
Ok(ReportDescriptor { value })
}
pub fn is_u2f_device(fd: RawFd) -> bool {
match read_report_descriptor(fd) {
Ok(desc) => has_fido_usage(desc),
Err(_) => false, // Upon failure, just say it's not a U2F device.
}
}

View File

@ -1,163 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Shared code for platforms that use raw HID access (Linux, FreeBSD, etc.)
#![cfg_attr(feature = "cargo-clippy", allow(cast_lossless, needless_lifetimes))]
use std::mem;
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
// The 4 MSBs (the tag) are set when it's a long item.
const HID_MASK_LONG_ITEM_TAG: u8 = 0b1111_0000;
// The 2 LSBs denote the size of a short item.
const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b0000_0011;
// The 6 MSBs denote the tag (4) and type (2).
const HID_MASK_ITEM_TAGTYPE: u8 = 0b1111_1100;
// tag=0000, type=10 (local)
const HID_ITEM_TAGTYPE_USAGE: u8 = 0b0000_1000;
// tag=0000, type=01 (global)
const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b0000_0100;
pub struct ReportDescriptor {
pub value: Vec<u8>,
}
impl ReportDescriptor {
fn iter(self) -> ReportDescriptorIterator {
ReportDescriptorIterator::new(self)
}
}
#[derive(Debug)]
pub enum Data {
UsagePage { data: u32 },
Usage { data: u32 },
}
pub struct ReportDescriptorIterator {
desc: ReportDescriptor,
pos: usize,
}
impl ReportDescriptorIterator {
fn new(desc: ReportDescriptor) -> Self {
Self { desc, pos: 0 }
}
fn next_item(&mut self) -> Option<Data> {
let item = get_hid_item(&self.desc.value[self.pos..]);
if item.is_none() {
self.pos = self.desc.value.len(); // Close, invalid data.
return None;
}
let (tag_type, key_len, data) = item.unwrap();
// Advance if we have a valid item.
self.pos += key_len + data.len();
// We only check short items.
if key_len > 1 {
return None; // Check next item.
}
// Short items have max. length of 4 bytes.
assert!(data.len() <= mem::size_of::<u32>());
// Convert data bytes to a uint.
let data = read_uint_le(data);
match tag_type {
HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
_ => None,
}
}
}
impl Iterator for ReportDescriptorIterator {
type Item = Data;
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.desc.value.len() {
return None;
}
self.next_item().or_else(|| self.next())
}
}
fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
get_hid_long_item(buf)
} else {
get_hid_short_item(buf)
}
}
fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
// A valid long item has at least three bytes.
if buf.len() < 3 {
return None;
}
let len = buf[1] as usize;
// Ensure that there are enough bytes left in the buffer.
if len > buf.len() - 3 {
return None;
}
Some((buf[2], 3 /* key length */, &buf[3..]))
}
fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
// This is a short item. The bottom two bits of the key
// contain the length of the data section (value) for this key.
let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
s @ 0...2 => s as usize,
_ => 4, /* _ == 3 */
};
// Ensure that there are enough bytes left in the buffer.
if len > buf.len() - 1 {
return None;
}
Some((
buf[0] & HID_MASK_ITEM_TAGTYPE,
1, /* key length */
&buf[1..=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) | (u32::from(*b)))
}
pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
let mut usage_page = None;
let mut usage = None;
for data in desc.iter() {
match data {
Data::UsagePage { data } => usage_page = Some(data),
Data::Usage { data } => usage = Some(data),
}
// Check the values we found.
if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
return usage_page == u32::from(FIDO_USAGE_PAGE)
&& usage == u32::from(FIDO_USAGE_U2FHID);
}
}
false
}

View File

@ -1,111 +0,0 @@
/* 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", target_os = "freebsd"))]
pub mod hidproto;
#[cfg(any(target_os = "linux"))]
extern crate libudev;
#[cfg(any(target_os = "linux"))]
#[path = "linux/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "freebsd"))]
extern crate devd_rs;
#[cfg(any(target_os = "freebsd"))]
#[path = "freebsd/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "macos"))]
extern crate core_foundation;
#[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(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "macos",
target_os = "windows"
)))]
#[path = "stub/mod.rs"]
pub mod platform;
extern crate boxfnonce;
extern crate libc;
#[macro_use]
extern crate log;
extern crate rand;
extern crate runloop;
#[macro_use]
extern crate bitflags;
mod consts;
mod statemachine;
mod u2fprotocol;
mod u2ftypes;
mod manager;
pub use manager::U2FManager;
mod capi;
pub use capi::*;
// Keep this in sync with the constants in u2fhid-capi.h.
bitflags! {
pub struct RegisterFlags: u64 {
const REQUIRE_RESIDENT_KEY = 1;
const REQUIRE_USER_VERIFICATION = 2;
const REQUIRE_PLATFORM_ATTACHMENT = 4;
}
}
bitflags! {
pub struct SignFlags: u64 {
const REQUIRE_USER_VERIFICATION = 1;
}
}
bitflags! {
pub struct AuthenticatorTransports: u8 {
const USB = 1;
const NFC = 2;
const BLE = 4;
}
}
#[derive(Clone)]
pub struct KeyHandle {
pub credential: Vec<u8>,
pub transports: AuthenticatorTransports,
}
pub type AppId = Vec<u8>;
pub type RegisterResult = Vec<u8>;
pub type SignResult = (AppId, Vec<u8>, Vec<u8>);
#[derive(Debug, Clone, Copy)]
pub enum Error {
Unknown = 1,
NotSupported = 2,
InvalidState = 3,
ConstraintError = 4,
NotAllowed = 5,
}
#[cfg(fuzzing)]
pub use consts::*;
#[cfg(fuzzing)]
pub use u2fprotocol::*;
#[cfg(fuzzing)]
pub use u2ftypes::*;

View File

@ -1,83 +0,0 @@
/* 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 u2ftypes::U2FDevice;
use util::from_unix_result;
#[derive(Debug)]
pub struct Device {
path: OsString,
fd: libc::c_int,
cid: [u8; 4],
}
impl Device {
pub fn new(path: OsString) -> io::Result<Self> {
let cstr = CString::new(path.as_bytes())?;
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
let fd = from_unix_result(fd)?;
Ok(Self {
path,
fd,
cid: CID_BROADCAST,
})
}
pub fn is_u2f(&self) -> bool {
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(&self) -> &[u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
}

View File

@ -1,81 +0,0 @@
/* 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/. */
#![cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
extern crate libc;
use std::io;
use std::mem;
use std::os::unix::io::RawFd;
use hidproto::*;
use util::{from_unix_result, io_err};
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct LinuxReportDescriptor {
size: ::libc::c_int,
value: [u8; 4096],
}
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;
// 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);
#[cfg(not(target_env = "musl"))]
type IocType = libc::c_ulong;
#[cfg(target_env = "musl")]
type IocType = libc::c_int;
from_unix_result(libc::ioctl(fd, ioc as IocType, 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*/ LinuxReportDescriptor);
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 = LinuxReportDescriptor {
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)? };
let mut value = Vec::from(&desc.value[..]);
value.truncate(desc.size as usize);
Ok(ReportDescriptor { value })
}

View File

@ -1,9 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod device;
pub mod transaction;
mod hidraw;
mod monitor;

View File

@ -1,133 +0,0 @@
/* 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::{c_int, c_short, c_ulong};
use libudev;
use libudev::EventType;
use runloop::RunLoop;
use std::collections::HashMap;
use std::ffi::OsString;
use std::io;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
const UDEV_SUBSYSTEM: &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 struct Monitor<F>
where
F: Fn(OsString, &Fn() -> bool) + Sync,
{
runloops: HashMap<OsString, RunLoop>,
new_device_cb: Arc<F>,
}
impl<F> Monitor<F>
where
F: Fn(OsString, &Fn() -> bool) + Send + Sync + 'static,
{
pub fn new(new_device_cb: F) -> Self {
Self {
runloops: HashMap::new(),
new_device_cb: Arc::new(new_device_cb),
}
}
pub fn run(&mut self, alive: &Fn() -> bool) -> io::Result<()> {
let 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()) {
self.add_device(path);
}
}
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,
}];
while alive() {
// Wait for new events, break on failure.
poll(&mut fds)?;
if let Some(event) = socket.receive_event() {
self.process_event(&event);
}
}
// Remove all tracked devices.
self.remove_all_devices();
Ok(())
}
fn process_event(&mut self, event: &libudev::Event) {
let path = event
.device()
.devnode()
.map(|dn| dn.to_owned().into_os_string());
match (event.event_type(), path) {
(EventType::Add, Some(path)) => {
self.add_device(path);
}
(EventType::Remove, Some(path)) => {
self.remove_device(&path);
}
_ => { /* ignore other types and failures */ }
}
}
fn add_device(&mut self, path: OsString) {
let f = self.new_device_cb.clone();
let key = path.clone();
let runloop = RunLoop::new(move |alive| {
if alive() {
f(path, alive);
}
});
if let Ok(runloop) = runloop {
self.runloops.insert(key, runloop);
}
}
fn remove_device(&mut self, path: &OsString) {
if let Some(runloop) = self.runloops.remove(path) {
runloop.cancel();
}
}
fn remove_all_devices(&mut self) {
while !self.runloops.is_empty() {
let path = self.runloops.keys().next().unwrap().clone();
self.remove_device(&path);
}
}
}

View File

@ -1,48 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use platform::monitor::Monitor;
use runloop::RunLoop;
use std::ffi::OsString;
use util::OnceCallback;
pub struct Transaction {
// Handle to the thread loop.
thread: Option<RunLoop>,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: OnceCallback<T>,
new_device_cb: F,
) -> Result<Self, ::Error>
where
F: Fn(OsString, &Fn() -> bool) + Sync + Send + 'static,
T: 'static,
{
let thread = RunLoop::new_with_timeout(
move |alive| {
// Create a new device monitor.
let mut monitor = Monitor::new(new_device_cb);
// Start polling for new devices.
try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
// Send an error, if the callback wasn't called already.
callback.call(Err(::Error::NotAllowed));
},
timeout,
).map_err(|_| ::Error::Unknown)?;
Ok(Self {
thread: Some(thread),
})
}
pub fn cancel(&mut self) {
// This must never be None.
self.thread.take().unwrap().cancel();
}
}

View File

@ -1,102 +0,0 @@
/* 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;
use consts::{CID_BROADCAST, HID_RPT_SIZE};
use core_foundation::base::*;
use platform::iokit::*;
use std::io;
use std::io::{Read, Write};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
use std::time::Duration;
use u2ftypes::U2FDevice;
const READ_TIMEOUT: u64 = 15;
pub struct Device {
device_ref: IOHIDDeviceRef,
cid: [u8; 4],
report_rx: Receiver<Vec<u8>>,
}
impl Device {
pub fn new(dev_info: (IOHIDDeviceRef, Receiver<Vec<u8>>)) -> io::Result<Self> {
let (device_ref, report_rx) = dev_info;
Ok(Self {
device_ref,
cid: CID_BROADCAST,
report_rx,
})
}
pub fn is_u2f(&self) -> bool {
true
}
}
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 = i64::from(bytes[0]);
// 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;
}
}

View File

@ -1,291 +0,0 @@
/* 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 libc;
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
use core_foundation::array::*;
use core_foundation::base::*;
use core_foundation::dictionary::*;
use core_foundation::number::*;
use core_foundation::runloop::*;
use core_foundation::string::*;
use std::ops::Deref;
use std::os::raw::c_void;
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: IOHIDDeviceRef,
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 {}
pub struct SendableRunLoop(CFRunLoopRef);
impl SendableRunLoop {
pub fn new(runloop: CFRunLoopRef) -> Self {
// Keep the CFRunLoop alive for as long as we are.
unsafe { CFRetain(runloop as *mut c_void) };
SendableRunLoop(runloop)
}
}
unsafe impl Send for SendableRunLoop {}
impl Deref for SendableRunLoop {
type Target = CFRunLoopRef;
fn deref(&self) -> &CFRunLoopRef {
&self.0
}
}
impl Drop for SendableRunLoop {
fn drop(&mut self) {
unsafe { CFRelease(self.0 as *mut c_void) };
}
}
#[repr(C)]
pub struct CFRunLoopObserverContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
pub release: Option<extern "C" fn(info: *const c_void)>,
pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
}
impl CFRunLoopObserverContext {
pub fn new(context: *mut c_void) -> Self {
Self {
version: 0 as CFIndex,
info: context,
retain: None,
release: None,
copyDescription: None,
}
}
}
pub struct CFRunLoopEntryObserver {
observer: CFRunLoopObserverRef,
// Keep alive until the observer goes away.
context_ptr: *mut CFRunLoopObserverContext,
}
impl CFRunLoopEntryObserver {
pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self {
let context = CFRunLoopObserverContext::new(context);
let context_ptr = Box::into_raw(Box::new(context));
let observer = unsafe {
CFRunLoopObserverCreate(
kCFAllocatorDefault,
kCFRunLoopEntry,
false as Boolean,
0,
callback,
context_ptr,
)
};
Self {
observer,
context_ptr,
}
}
pub fn add_to_current_runloop(&self) {
unsafe {
CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode)
};
}
}
impl Drop for CFRunLoopEntryObserver {
fn drop(&mut self) {
unsafe {
CFRelease(self.observer as *mut c_void);
// Drop the CFRunLoopObserverContext.
let _ = Box::from_raw(self.context_ptr);
};
}
}
pub struct IOHIDDeviceMatcher {
pub dict: CFDictionary<CFString, CFNumber>,
}
impl IOHIDDeviceMatcher {
pub fn new() -> Self {
let dict = CFDictionary::<CFString, CFNumber>::from_CFType_pairs(&[
(
CFString::from_static_string("DeviceUsage"),
CFNumber::from(i32::from(FIDO_USAGE_U2FHID)),
),
(
CFString::from_static_string("DeviceUsagePage"),
CFNumber::from(i32::from(FIDO_USAGE_PAGE)),
),
]);
Self { dict }
}
}
#[link(name = "IOKit", kind = "framework")]
extern "C" {
// CFRunLoop
pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) -> CFRunLoopObserverRef;
// 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 IOHIDManagerRegisterInputReportCallback(
manager: IOHIDManagerRef,
callback: IOHIDReportCallback,
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;
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use super::*;
use std::os::raw::c_void;
use std::ptr;
use std::sync::mpsc::{channel, Sender};
use std::thread;
extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
// Send the current runloop to the receiver to unblock it.
let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() }));
}
#[test]
fn test_sendable_runloop() {
let (tx, rx) = channel();
let thread = thread::spawn(move || {
// Send the runloop to the owning thread.
let context = &tx as *const _ as *mut c_void;
let obs = CFRunLoopEntryObserver::new(observe, context);
obs.add_to_current_runloop();
unsafe {
// We need some source for the runloop to run.
let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0);
assert!(!manager.is_null());
IOHIDManagerScheduleWithRunLoop(
manager,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode,
);
IOHIDManagerSetDeviceMatching(manager, ptr::null_mut());
let rv = IOHIDManagerOpen(manager, 0);
assert_eq!(rv, 0);
// This will run until `CFRunLoopStop()` is called.
CFRunLoopRun();
let rv = IOHIDManagerClose(manager, 0);
assert_eq!(rv, 0);
CFRelease(manager as *mut c_void);
}
});
// Block until we enter the CFRunLoop.
let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop");
// Stop the runloop.
unsafe { CFRunLoopStop(*runloop) };
// Stop the thread.
thread.join().expect("failed to join the thread");
// Try to stop the runloop again (without crashing).
unsafe { CFRunLoopStop(*runloop) };
}
}

View File

@ -1,9 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod device;
pub mod transaction;
mod iokit;
mod monitor;

View File

@ -1,175 +0,0 @@
/* 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 core_foundation::base::*;
use core_foundation::runloop::*;
use platform::iokit::*;
use runloop::RunLoop;
use std::collections::HashMap;
use std::os::raw::c_void;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::{io, slice};
use util::io_err;
struct DeviceData {
tx: Sender<Vec<u8>>,
runloop: RunLoop,
}
pub struct Monitor<F>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &Fn() -> bool) + Sync,
{
manager: IOHIDManagerRef,
// Keep alive until the monitor goes away.
_matcher: IOHIDDeviceMatcher,
map: HashMap<IOHIDDeviceRef, DeviceData>,
new_device_cb: F,
}
impl<F> Monitor<F>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &Fn() -> bool) + Sync + 'static,
{
pub fn new(new_device_cb: F) -> Self {
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
// Match FIDO devices only.
let _matcher = IOHIDDeviceMatcher::new();
unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.dict.as_concrete_TypeRef()) };
Self {
manager,
_matcher,
new_device_cb,
map: HashMap::new(),
}
}
pub fn start(&mut self) -> io::Result<()> {
let context = self as *mut Self as *mut c_void;
unsafe {
IOHIDManagerRegisterDeviceMatchingCallback(
self.manager,
Monitor::<F>::on_device_matching,
context,
);
IOHIDManagerRegisterDeviceRemovalCallback(
self.manager,
Monitor::<F>::on_device_removal,
context,
);
IOHIDManagerRegisterInputReportCallback(
self.manager,
Monitor::<F>::on_input_report,
context,
);
IOHIDManagerScheduleWithRunLoop(
self.manager,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode,
);
let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone);
if rv == 0 {
Ok(())
} else {
Err(io_err(&format!("Couldn't open HID Manager, rv={}", rv)))
}
}
}
pub fn stop(&mut self) {
// Remove all devices.
while !self.map.is_empty() {
let device_ref = *self.map.keys().next().unwrap();
self.remove_device(device_ref);
}
// Close the manager and its devices.
unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
}
fn remove_device(&mut self, device_ref: IOHIDDeviceRef) {
if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) {
// Dropping `tx` will make Device::read() fail eventually.
drop(tx);
// Wait until the runloop stopped.
runloop.cancel();
}
}
extern "C" fn on_input_report(
context: *mut c_void,
_: IOReturn,
device_ref: IOHIDDeviceRef,
_: IOHIDReportType,
_: u32,
report: *mut u8,
report_len: CFIndex,
) {
let this = unsafe { &mut *(context as *mut Self) };
let mut send_failed = false;
// Ignore the report if we can't find a device for it.
if let Some(&DeviceData { ref tx, .. }) = this.map.get(&device_ref) {
let data = unsafe { slice::from_raw_parts(report, report_len as usize).to_vec() };
send_failed = tx.send(data).is_err();
}
// Remove the device if sending fails.
if send_failed {
this.remove_device(device_ref);
}
}
extern "C" fn on_device_matching(
context: *mut c_void,
_: IOReturn,
_: *mut c_void,
device_ref: IOHIDDeviceRef,
) {
let this = unsafe { &mut *(context as *mut Self) };
let (tx, rx) = channel();
let f = &this.new_device_cb;
// Create a new per-device runloop.
let runloop = RunLoop::new(move |alive| {
// Ensure that the runloop is still alive.
if alive() {
f((device_ref, rx), alive);
}
});
if let Ok(runloop) = runloop {
this.map.insert(device_ref, DeviceData { tx, runloop });
}
}
extern "C" fn on_device_removal(
context: *mut c_void,
_: IOReturn,
_: *mut c_void,
device_ref: IOHIDDeviceRef,
) {
let this = unsafe { &mut *(context as *mut Self) };
this.remove_device(device_ref);
}
}
impl<F> Drop for Monitor<F>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &Fn() -> bool) + Sync,
{
fn drop(&mut self) {
unsafe { CFRelease(self.manager as *mut c_void) };
}
}

View File

@ -1,85 +0,0 @@
/* 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 core_foundation::runloop::*;
use platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop};
use platform::monitor::Monitor;
use std::os::raw::c_void;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use util::OnceCallback;
// A transaction will run the given closure in a new thread, thereby using a
// separate per-thread state machine for each HID. It will either complete or
// fail through user action, timeout, or be cancelled when overridden by a new
// transaction.
pub struct Transaction {
runloop: Option<SendableRunLoop>,
thread: Option<thread::JoinHandle<()>>,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: OnceCallback<T>,
new_device_cb: F,
) -> Result<Self, ::Error>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &Fn() -> bool) + Sync + Send + 'static,
T: 'static,
{
let (tx, rx) = channel();
let timeout = (timeout as f64) / 1000.0;
let builder = thread::Builder::new();
let thread = builder
.spawn(move || {
// Add a runloop observer that will be notified when we enter the
// runloop and tx.send() the current runloop to the owning thread.
// We need to ensure the runloop was entered before unblocking
// Transaction::new(), so we can always properly cancel.
let context = &tx as *const _ as *mut c_void;
let obs = CFRunLoopEntryObserver::new(Transaction::observe, context);
obs.add_to_current_runloop();
// Create a new HID device monitor and start polling.
let mut monitor = Monitor::new(new_device_cb);
try_or!(monitor.start(), |_| callback.call(Err(::Error::Unknown)));
// This will block until completion, abortion, or timeout.
unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) };
// Close the monitor and its devices.
monitor.stop();
// Send an error, if the callback wasn't called already.
callback.call(Err(::Error::NotAllowed));
}).map_err(|_| ::Error::Unknown)?;
// Block until we enter the CFRunLoop.
let runloop = rx.recv().map_err(|_| ::Error::Unknown)?;
Ok(Self {
runloop: Some(runloop),
thread: Some(thread),
})
}
extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
// Send the current runloop to the receiver to unblock it.
let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() }));
}
pub fn cancel(&mut self) {
// This must never be None. This won't block.
unsafe { CFRunLoopStop(*self.runloop.take().unwrap()) };
// This must never be None. Ignore return value.
let _ = self.thread.take().unwrap().join();
}
}

View File

@ -1,188 +0,0 @@
/* 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, RecvTimeoutError, Sender};
use std::time::Duration;
use consts::PARAMETER_SIZE;
use runloop::RunLoop;
use statemachine::StateMachine;
use util::OnceCallback;
enum QueueAction {
Register {
flags: ::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: ::AppId,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<::RegisterResult>,
},
Sign {
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<::AppId>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<::SignResult>,
},
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 = RunLoop::new(move |alive| {
let mut sm = StateMachine::new();
while alive() {
match rx.recv_timeout(Duration::from_millis(50)) {
Ok(QueueAction::Register {
flags,
timeout,
challenge,
application,
key_handles,
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.register(
flags,
timeout,
challenge,
application,
key_handles,
callback,
);
}
Ok(QueueAction::Sign {
flags,
timeout,
challenge,
app_ids,
key_handles,
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.sign(flags, timeout, challenge, app_ids, 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.
sm.cancel();
}
Err(RecvTimeoutError::Disconnected) => {
break;
}
_ => { /* continue */ }
}
}
// Cancel any ongoing activity.
sm.cancel();
})?;
Ok(Self { queue, tx })
}
pub fn register<F>(
&self,
flags: ::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: ::AppId,
key_handles: Vec<::KeyHandle>,
callback: F,
) -> Result<(), ::Error>
where
F: FnOnce(Result<::RegisterResult, ::Error>),
F: Send + 'static,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
return Err(::Error::Unknown);
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
return Err(::Error::Unknown);
}
}
let callback = OnceCallback::new(callback);
let action = QueueAction::Register {
flags,
timeout,
challenge,
application,
key_handles,
callback,
};
self.tx.send(action).map_err(|_| ::Error::Unknown)
}
pub fn sign<F>(
&self,
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<::AppId>,
key_handles: Vec<::KeyHandle>,
callback: F,
) -> Result<(), ::Error>
where
F: FnOnce(Result<::SignResult, ::Error>),
F: Send + 'static,
{
if challenge.len() != PARAMETER_SIZE {
return Err(::Error::Unknown);
}
if app_ids.is_empty() {
return Err(::Error::Unknown);
}
for app_id in &app_ids {
if app_id.len() != PARAMETER_SIZE {
return Err(::Error::Unknown);
}
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
return Err(::Error::Unknown);
}
}
let callback = OnceCallback::new(callback);
let action = QueueAction::Sign {
flags,
timeout,
challenge,
app_ids,
key_handles,
callback,
};
self.tx.send(action).map_err(|_| ::Error::Unknown)
}
pub fn cancel(&self) -> Result<(), ::Error> {
self.tx
.send(QueueAction::Cancel)
.map_err(|_| ::Error::Unknown)
}
}
impl Drop for U2FManager {
fn drop(&mut self) {
self.queue.cancel();
}
}

View File

@ -1,215 +0,0 @@
/* 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 consts::PARAMETER_SIZE;
use platform::device::Device;
use platform::transaction::Transaction;
use std::thread;
use std::time::Duration;
use u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
use util::OnceCallback;
fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
}
fn find_valid_key_handles<'a, F>(
app_ids: &'a [::AppId],
key_handles: &'a [::KeyHandle],
mut is_valid: F,
) -> (&'a ::AppId, Vec<&'a ::KeyHandle>)
where
F: FnMut(&Vec<u8>, &::KeyHandle) -> bool,
{
// Try all given app_ids in order.
for app_id in app_ids {
// Find all valid key handles for the current app_id.
let valid_handles = key_handles
.iter()
.filter(|key_handle| is_valid(app_id, key_handle))
.collect::<Vec<_>>();
// If there's at least one, stop.
if !valid_handles.is_empty() {
return (app_id, valid_handles);
}
}
(&app_ids[0], vec![])
}
#[derive(Default)]
pub struct StateMachine {
transaction: Option<Transaction>,
}
impl StateMachine {
pub fn new() -> Self {
Default::default()
}
pub fn register(
&mut self,
flags: ::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: ::AppId,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<::RegisterResult>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// We currently support none of the authenticator selection
// criteria because we can't ask tokens whether they do support
// those features. If flags are set, ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
// Iterate the exclude list and see if there are any matches.
// If so, we'll keep polling the device anyway to test for user
// consent, to be consistent with CTAP2 device behavior.
let excluded = key_handles.iter().any(|key_handle| {
is_valid_transport(key_handle.transports) && u2f_is_keyhandle_valid(
dev,
&challenge,
&application,
&key_handle.credential,
).unwrap_or(false) /* no match on failure */
});
while alive() {
if excluded {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(::Error::InvalidState));
break;
}
} else if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
callback.call(Ok(bytes));
break;
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
});
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
pub fn sign(
&mut self,
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<::AppId>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<::SignResult>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// We currently don't support user verification because we can't
// ask tokens whether they do support that. If the flag is set,
// ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
// For each appId, try all key handles. If there's at least one
// valid key handle for an appId, we'll use that appId below.
let (app_id, valid_handles) =
find_valid_key_handles(&app_ids, &key_handles, |app_id, key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, app_id, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
});
// Aggregate distinct transports from all given credentials.
let transports = key_handles
.iter()
.fold(::AuthenticatorTransports::empty(), |t, k| t | k.transports);
// We currently only support USB. If the RP specifies transports
// and doesn't include USB it's probably lying.
if !is_valid_transport(transports) {
return;
}
while alive() {
// If the device matches none of the given key handles
// then just make it blink with bogus data.
if valid_handles.is_empty() {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(::Error::InvalidState));
break;
}
} else {
// Otherwise, try to sign.
for key_handle in &valid_handles {
if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential)
{
callback.call(Ok((
app_id.clone(),
key_handle.credential.clone(),
bytes,
)));
break;
}
}
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
});
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
// This blocks.
pub fn cancel(&mut self) {
if let Some(mut transaction) = self.transaction.take() {
transaction.cancel();
}
}
}

View File

@ -1,45 +0,0 @@
/* 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::io::{Read, Write};
use u2ftypes::U2FDevice;
pub struct Device {}
impl Device {
pub fn new(path: String) -> io::Result<Self> {
panic!("not implemented");
}
pub fn is_u2f(&self) -> bool {
panic!("not implemented");
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
panic!("not implemented");
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
panic!("not implemented");
}
fn flush(&mut self) -> io::Result<()> {
panic!("not implemented");
}
}
impl U2FDevice for Device {
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
panic!("not implemented");
}
fn set_cid(&mut self, cid: [u8; 4]) {
panic!("not implemented");
}
}

View File

@ -1,11 +0,0 @@
/* 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(unused_variables)]
pub mod device;
pub mod transaction;

View File

@ -1,25 +0,0 @@
/* 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 util::OnceCallback;
pub struct Transaction {}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: OnceCallback<T>,
new_device_cb: F,
) -> Result<Self, ::Error>
where
F: Fn(String, &Fn() -> bool),
{
callback.call(Err(::Error::NotSupported));
Err(::Error::NotSupported)
}
pub fn cancel(&mut self) {
/* No-op. */
}
}

View File

@ -1,106 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=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;
const uint8_t U2F_RESBUF_ID_APPID = 3;
const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
const uint64_t U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT = 4;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_USB = 1;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
const uint8_t CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL = 8;
const uint8_t U2F_ERROR_UKNOWN = 1;
const uint8_t U2F_ERROR_NOT_SUPPORTED = 2;
const uint8_t U2F_ERROR_INVALID_STATE = 3;
const uint8_t U2F_ERROR_CONSTRAINT = 4;
const uint8_t U2F_ERROR_NOT_ALLOWED = 5;
// 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_app_ids` opaque type is equivalent to the rust type `U2FAppIds`
struct rust_u2f_app_ids;
// 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 flags,
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_sign(rust_u2f_manager* mgr, uint64_t flags,
uint64_t timeout, rust_u2f_callback,
const uint8_t* challenge_ptr, size_t challenge_len,
const rust_u2f_app_ids* app_ids,
const rust_u2f_key_handles* khs);
void rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
/// U2FAppIds functions.
rust_u2f_app_ids* rust_u2f_app_ids_new();
void rust_u2f_app_ids_add(rust_u2f_app_ids* ids, const uint8_t* id,
size_t id_len);
/* unsafe */ void rust_u2f_app_ids_free(rust_u2f_app_ids* ids);
/// 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, uint8_t transports);
/* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);
/// U2FResult functions.
// Returns 0 for success, or the U2F_ERROR error code >= 1.
uint8_t rust_u2f_result_error(const rust_u2f_result* res);
// 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

View File

@ -1,401 +0,0 @@
/* 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/. */
#![cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))]
extern crate std;
use rand::{thread_rng, RngCore};
use std::ffi::CString;
use std::io;
use std::io::{Read, Write};
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,
{
let mut nonce = [0u8; 8];
thread_rng().fill_bytes(&mut nonce);
// Initialize the device and check its version.
init_device(dev, &nonce).is_ok() && 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 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::serialize(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, RngCore};
use super::{init_device, send_apdu, sendrecv, U2FDevice};
use consts::{CID_BROADCAST, SW_NO_ERROR, U2FHID_INIT, U2FHID_MSG, U2FHID_PING};
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()].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.is_empty(), "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.is_empty(), "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!(self.reads.is_empty());
assert!(self.writes.is_empty());
}
}
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);
// 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 remain
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);
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);
}
}

View File

@ -1,187 +0,0 @@
/* 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::Level::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 serialize(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)
}
}

View File

@ -1,94 +0,0 @@
/* 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::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)
}
}
#[cfg(any(target_os = "freebsd"))]
pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
if rv.is_negative() {
let errno = unsafe { *libc::__error() };
Err(io::Error::from_raw_os_error(errno))
} else {
Ok(rv)
}
}
pub fn io_err(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::Other, msg)
}
pub struct OnceCallback<T> {
callback: Arc<Mutex<Option<SendBoxFnOnce<(Result<T, ::Error>,)>>>>,
}
impl<T> OnceCallback<T> {
pub fn new<F>(cb: F) -> Self
where
F: FnOnce(Result<T, ::Error>),
F: Send + 'static,
{
let cb = Some(SendBoxFnOnce::from(cb));
Self {
callback: Arc::new(Mutex::new(cb)),
}
}
pub fn call(&self, rv: Result<T, ::Error>) {
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(),
}
}
}

View File

@ -1,74 +0,0 @@
/* 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 super::winapi::DeviceCapabilities;
use consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, HID_RPT_SIZE};
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;
}
}

View File

@ -1,9 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod device;
pub mod transaction;
mod monitor;
mod winapi;

View File

@ -1,91 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use platform::winapi::DeviceInfoSet;
use runloop::RunLoop;
use std::collections::{HashMap, HashSet};
use std::io;
use std::iter::FromIterator;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
pub struct Monitor<F>
where
F: Fn(String, &Fn() -> bool) + Sync,
{
runloops: HashMap<String, RunLoop>,
new_device_cb: Arc<F>,
}
impl<F> Monitor<F>
where
F: Fn(String, &Fn() -> bool) + Send + Sync + 'static,
{
pub fn new(new_device_cb: F) -> Self {
Self {
runloops: HashMap::new(),
new_device_cb: Arc::new(new_device_cb),
}
}
pub fn run(&mut self, alive: &Fn() -> bool) -> io::Result<()> {
let mut 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) {
self.remove_device(path);
}
// Add devices that were plugged in.
for path in devices.difference(&stored) {
self.add_device(path);
}
// Remember the new set.
stored = devices;
// Wait a little before looking for devices again.
thread::sleep(Duration::from_millis(100));
}
// Remove all tracked devices.
self.remove_all_devices();
Ok(())
}
fn add_device(&mut self, path: &String) {
let f = self.new_device_cb.clone();
let path = path.clone();
let key = path.clone();
let runloop = RunLoop::new(move |alive| {
if alive() {
f(path, alive);
}
});
if let Ok(runloop) = runloop {
self.runloops.insert(key, runloop);
}
}
fn remove_device(&mut self, path: &String) {
if let Some(runloop) = self.runloops.remove(path) {
runloop.cancel();
}
}
fn remove_all_devices(&mut self) {
while !self.runloops.is_empty() {
let path = self.runloops.keys().next().unwrap().clone();
self.remove_device(&path);
}
}
}

View File

@ -1,47 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use platform::monitor::Monitor;
use runloop::RunLoop;
use util::OnceCallback;
pub struct Transaction {
// Handle to the thread loop.
thread: Option<RunLoop>,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: OnceCallback<T>,
new_device_cb: F,
) -> Result<Self, ::Error>
where
F: Fn(String, &Fn() -> bool) + Sync + Send + 'static,
T: 'static,
{
let thread = RunLoop::new_with_timeout(
move |alive| {
// Create a new device monitor.
let mut monitor = Monitor::new(new_device_cb);
// Start polling for new devices.
try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
// Send an error, if the callback wasn't called already.
callback.call(Err(::Error::NotAllowed));
},
timeout,
).map_err(|_| ::Error::Unknown)?;
Ok(Self {
thread: Some(thread),
})
}
pub fn cancel(&mut self) {
// This must never be None.
self.thread.take().unwrap().cancel();
}
}

View File

@ -1,271 +0,0 @@
/* 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 platform::winapi::winapi::shared::{guiddef, minwindef, ntdef, windef};
use platform::winapi::winapi::shared::{hidclass, hidpi, hidusage};
use platform::winapi::winapi::um::{handleapi, setupapi};
#[link(name = "setupapi")]
extern "system" {
fn SetupDiGetClassDevsW(
ClassGuid: *const guiddef::GUID,
Enumerator: ntdef::PCSTR,
hwndParent: windef::HWND,
flags: minwindef::DWORD,
) -> setupapi::HDEVINFO;
fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: setupapi::HDEVINFO) -> minwindef::BOOL;
fn SetupDiEnumDeviceInterfaces(
DeviceInfoSet: setupapi::HDEVINFO,
DeviceInfoData: setupapi::PSP_DEVINFO_DATA,
InterfaceClassGuid: *const guiddef::GUID,
MemberIndex: minwindef::DWORD,
DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA,
) -> minwindef::BOOL;
fn SetupDiGetDeviceInterfaceDetailW(
DeviceInfoSet: setupapi::HDEVINFO,
DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA,
DeviceInterfaceDetailData: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
DeviceInterfaceDetailDataSize: minwindef::DWORD,
RequiredSize: minwindef::PDWORD,
DeviceInfoData: setupapi::PSP_DEVINFO_DATA,
) -> minwindef::BOOL;
}
#[link(name = "hid")]
extern "system" {
fn HidD_GetPreparsedData(
HidDeviceObject: ntdef::HANDLE,
PreparsedData: *mut hidpi::PHIDP_PREPARSED_DATA,
) -> ntdef::BOOLEAN;
fn HidD_FreePreparsedData(PreparsedData: hidpi::PHIDP_PREPARSED_DATA) -> ntdef::BOOLEAN;
fn HidP_GetCaps(
PreparsedData: hidpi::PHIDP_PREPARSED_DATA,
Capabilities: hidpi::PHIDP_CAPS,
) -> ntdef::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: setupapi::HDEVINFO,
}
impl DeviceInfoSet {
pub fn new() -> io::Result<Self> {
let flags = setupapi::DIGCF_PRESENT | setupapi::DIGCF_DEVICEINTERFACE;
let set = unsafe {
SetupDiGetClassDevsW(
&hidclass::GUID_DEVINTERFACE_HID,
ptr::null_mut(),
ptr::null_mut(),
flags,
)
};
if set == handleapi::INVALID_HANDLE_VALUE {
return Err(io_err("SetupDiGetClassDevsW failed!"));
}
Ok(Self { set })
}
pub fn get(&self) -> setupapi::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: minwindef::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::<setupapi::SP_DEVICE_INTERFACE_DATA>() };
device_interface_data.cbSize =
mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DATA>() as minwindef::UINT;
let rv = unsafe {
SetupDiEnumDeviceInterfaces(
self.set.get(),
ptr::null_mut(),
&hidclass::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: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
path_len: usize,
}
impl DeviceInterfaceDetailData {
fn new(size: usize) -> Option<Self> {
let mut cb_size = mem::size_of::<setupapi::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 data = unsafe { libc::malloc(size) as setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W };
if data.is_null() {
return None;
}
// Set total size of the structure.
unsafe { (*data).cbSize = cb_size as minwindef::UINT };
// Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`.
let offset = offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
Some(Self {
data,
path_len: size - offset,
})
}
fn get(&self) -> setupapi::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: hidpi::HIDP_CAPS,
}
impl DeviceCapabilities {
pub fn new(handle: ntdef::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: hidpi::HIDP_CAPS = unsafe { mem::uninitialized() };
unsafe {
let rv = HidP_GetCaps(preparsed_data, &mut caps);
HidD_FreePreparsedData(preparsed_data);
if rv != hidpi::HIDP_STATUS_SUCCESS {
return Err(io_err("HidP_GetCaps failed!"));
}
}
Ok(Self { caps })
}
pub fn usage(&self) -> hidusage::USAGE {
self.caps.Usage
}
pub fn usage_page(&self) -> hidusage::USAGE {
self.caps.UsagePage
}
}

View File

@ -24,7 +24,7 @@ encoding_c = "0.9.0"
encoding_glue = { path = "../../../../intl/encoding_glue" }
audioipc-client = { path = "../../../../media/audioipc/client", optional = true }
audioipc-server = { path = "../../../../media/audioipc/server", optional = true }
u2fhid = { path = "../../../../dom/webauthn/u2f-hid-rs" }
authenticator = "0.2.6"
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
rsdparsa_capi = { path = "../../../../media/webrtc/signaling/src/sdp/rsdparsa_capi" }
xulstore = { path = "../../../components/xulstore", optional = true }

View File

@ -28,7 +28,7 @@ extern crate audioipc_client;
#[cfg(feature = "cubeb-remoting")]
extern crate audioipc_server;
extern crate env_logger;
extern crate u2fhid;
extern crate authenticator;
extern crate gkrust_utils;
extern crate log;
#[cfg(feature = "new_cert_storage")]