зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1388843 - Part 1: Copy u2f-hid-rs into dom/webauthn/ r=gerv,qdot
This commit is contained in:
Родитель
7c3902243d
Коммит
dd24dc77e0
|
@ -0,0 +1,9 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
.DS_Store
|
|
@ -0,0 +1,30 @@
|
||||||
|
sudo: false
|
||||||
|
language: rust
|
||||||
|
cache: cargo
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- build-essential
|
||||||
|
- libudev-dev
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- pkg-config --list-all
|
||||||
|
- pkg-config --libs libudev
|
||||||
|
- pkg-config --modversion libudev
|
||||||
|
- cargo install rustfmt || true
|
||||||
|
|
||||||
|
script:
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_RUST_VERSION" == "nightly" ] ; then
|
||||||
|
export ASAN_OPTIONS="detect_odr_violation=1:leak_check_at_exit=0:detect_leaks=0"
|
||||||
|
export RUSTFLAGS="-Z sanitizer=address"
|
||||||
|
fi
|
||||||
|
- |
|
||||||
|
cargo fmt -- --write-mode=diff &&
|
||||||
|
cargo build &&
|
||||||
|
cargo test
|
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "u2fhid"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Kyle Machulis <kyle@nonpolynomial.com>", "J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>"]
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
libudev = "^0.2"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
core-foundation-sys = "0.3.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
winapi = "0.2.8"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.3"
|
||||||
|
log = "0.3"
|
||||||
|
env_logger = "0.4.1"
|
||||||
|
libc = "^0.2"
|
||||||
|
boxfnonce = "0.0.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rust-crypto = "^0.2"
|
||||||
|
base64 = "^0.4"
|
|
@ -0,0 +1,374 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# A Rust HID library for interacting with U2F Security Keys
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/jcjones/u2f-hid-rs.svg?branch=master)](https://travis-ci.org/jcjones/u2f-hid-rs)
|
||||||
|
![Maturity Level](https://img.shields.io/badge/maturity-beta-yellow.svg)
|
||||||
|
|
||||||
|
This is a cross-platform library for interacting with U2F Security Key-type devices via Rust.
|
||||||
|
|
||||||
|
* **Supported Platforms**: Windows, Linux, and Mac OS X.
|
||||||
|
* **Supported HID Transports**: USB.
|
||||||
|
* **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html).
|
||||||
|
|
||||||
|
This library currently focuses on U2F security keys, but is expected to be extended to
|
||||||
|
support additional protocols and transports.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
There's only a simple example function that tries to register and sign right now. It uses
|
||||||
|
[env_logger](http://rust-lang-nursery.github.io/log/env_logger/) for logging, which you
|
||||||
|
configure with the `RUST_LOG` environment variable:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo build
|
||||||
|
RUST_LOG=debug cargo run --example main
|
||||||
|
```
|
||||||
|
|
||||||
|
Proper usage should be to call into this library from something else - e.g., Firefox. There are
|
||||||
|
some [C headers exposed for the purpose](u2f-hid-rs/blob/master/src/u2fhid-capi.h).
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
There are some tests of the cross-platform runloop logic and the protocol decoder:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fuzzing
|
||||||
|
|
||||||
|
There are fuzzers for the USB protocol reader, basically fuzzing inputs from the HID layer.
|
||||||
|
There are not (yet) fuzzers for the C API used by callers (such as Gecko).
|
||||||
|
|
||||||
|
To fuzz, you will need cargo-fuzz (the latest version from GitHub) as well as Rust Nightly.
|
||||||
|
|
||||||
|
```
|
||||||
|
rustup install nightly
|
||||||
|
cargo install --git https://github.com/rust-fuzz/cargo-fuzz/
|
||||||
|
|
||||||
|
rustup run nightly cargo fuzz run u2f_read -- -max_len=512
|
||||||
|
rustup run nightly cargo fuzz run u2f_read_write -- -max_len=512
|
||||||
|
```
|
|
@ -0,0 +1,8 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(any(target_os = "macos"))]
|
||||||
|
println!("cargo:rustc-link-lib=framework=IOKit");
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate base64;
|
||||||
|
extern crate crypto;
|
||||||
|
extern crate u2fhid;
|
||||||
|
use crypto::digest::Digest;
|
||||||
|
use crypto::sha2::Sha256;
|
||||||
|
use std::io;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use u2fhid::U2FManager;
|
||||||
|
|
||||||
|
extern crate log;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
fn u2f_get_key_handle_from_register_response(register_response: &Vec<u8>) -> io::Result<Vec<u8>> {
|
||||||
|
if register_response[0] != 0x05 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"Reserved byte not set correctly",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_handle_len = register_response[66] as usize;
|
||||||
|
let mut public_key = register_response.clone();
|
||||||
|
let mut key_handle = public_key.split_off(67);
|
||||||
|
let _attestation = key_handle.split_off(key_handle_len);
|
||||||
|
|
||||||
|
Ok(key_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init().expect("Cannot start logger");
|
||||||
|
|
||||||
|
println!("Asking a security key to register now...");
|
||||||
|
let challenge_str =
|
||||||
|
format!("{}{}",
|
||||||
|
r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
|
||||||
|
r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"#);
|
||||||
|
let mut challenge = Sha256::new();
|
||||||
|
challenge.input_str(&challenge_str);
|
||||||
|
let mut chall_bytes: Vec<u8> = vec![0; challenge.output_bytes()];
|
||||||
|
challenge.result(&mut chall_bytes);
|
||||||
|
|
||||||
|
let mut application = Sha256::new();
|
||||||
|
application.input_str("http://demo.yubico.com");
|
||||||
|
let mut app_bytes: Vec<u8> = vec![0; application.output_bytes()];
|
||||||
|
application.result(&mut app_bytes);
|
||||||
|
|
||||||
|
let manager = U2FManager::new().unwrap();
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
manager
|
||||||
|
.register(15_000, chall_bytes.clone(), app_bytes.clone(), move |rv| {
|
||||||
|
tx.send(rv.unwrap()).unwrap();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let register_data = rx.recv().unwrap();
|
||||||
|
println!("Register result: {}", base64::encode(®ister_data));
|
||||||
|
println!("Asking a security key to sign now, with the data from the register...");
|
||||||
|
let key_handle = u2f_get_key_handle_from_register_response(®ister_data).unwrap();
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
manager
|
||||||
|
.sign(
|
||||||
|
15_000,
|
||||||
|
chall_bytes,
|
||||||
|
app_bytes,
|
||||||
|
vec![key_handle],
|
||||||
|
move |rv| { tx.send(rv.unwrap()).unwrap(); },
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (_, sign_data) = rx.recv().unwrap();
|
||||||
|
println!("Sign result: {}", base64::encode(&sign_data));
|
||||||
|
println!("Done.");
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
artifacts
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "u2fhid-fuzz"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Automatically generated"]
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.3"
|
||||||
|
|
||||||
|
[dependencies.u2fhid]
|
||||||
|
path = ".."
|
||||||
|
[dependencies.libfuzzer-sys]
|
||||||
|
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
|
||||||
|
|
||||||
|
# Prevent this from interfering with workspaces
|
||||||
|
[workspace]
|
||||||
|
members = ["."]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "u2f_read"
|
||||||
|
path = "fuzz_targets/u2f_read.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "u2f_read_write"
|
||||||
|
path = "fuzz_targets/u2f_read_write.rs"
|
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/1172fb09a3c99ed54c7d677faae5386d938d5834
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/1172fb09a3c99ed54c7d677faae5386d938d5834
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/3e28ec02ceb803efa105dc82fcfc9cb4014d7ced
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/3e28ec02ceb803efa105dc82fcfc9cb4014d7ced
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5232969ad85258a14c3fbdb7b3e112539755f3a4
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5232969ad85258a14c3fbdb7b3e112539755f3a4
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5814556fab57a61da7d4150c2b45c15686df25ec
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5814556fab57a61da7d4150c2b45c15686df25ec
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/60c1a897bef5145eb977461d13f789e1ddfa4a69
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/60c1a897bef5145eb977461d13f789e1ddfa4a69
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/7f960b178bcf3adf082da7c632b4e1366ba22274
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/7f960b178bcf3adf082da7c632b4e1366ba22274
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a22d2b1daa9483abc026fa2f75290aa51be59ca9
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a22d2b1daa9483abc026fa2f75290aa51be59ca9
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a5142fc55a5d72c9978bd54c73c8e32743bd8cfe
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a5142fc55a5d72c9978bd54c73c8e32743bd8cfe
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/ab4368e52f60dbe956d28100adf2a4d110b615ec
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/ab4368e52f60dbe956d28100adf2a4d110b615ec
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/1172fb09a3c99ed54c7d677faae5386d938d5834
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/1172fb09a3c99ed54c7d677faae5386d938d5834
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/62b74bc0ad2433f77a007b95eaf964ea719d21e2
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/62b74bc0ad2433f77a007b95eaf964ea719d21e2
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/680d38f668ab4372aaf0d57910c30a5ce6a6a03d
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/680d38f668ab4372aaf0d57910c30a5ce6a6a03d
Normal file
Двоичный файл не отображается.
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/f94c8e14f32ca38e773745c5622e2da2a18c3a47
Normal file
Двоичные данные
dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/f94c8e14f32ca38e773745c5622e2da2a18c3a47
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,65 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
#[macro_use] extern crate libfuzzer_sys;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate u2fhid;
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::{cmp, io};
|
||||||
|
|
||||||
|
use u2fhid::{CID_BROADCAST, HID_RPT_SIZE};
|
||||||
|
use u2fhid::{U2FDevice, sendrecv};
|
||||||
|
|
||||||
|
struct TestDevice<'a> {
|
||||||
|
cid: [u8; 4],
|
||||||
|
data: &'a [u8]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TestDevice<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> TestDevice {
|
||||||
|
TestDevice {
|
||||||
|
cid: CID_BROADCAST,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> io::Read for TestDevice<'a> {
|
||||||
|
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
|
||||||
|
assert!(bytes.len() == HID_RPT_SIZE);
|
||||||
|
let max = cmp::min(self.data.len(), HID_RPT_SIZE);
|
||||||
|
bytes[..max].copy_from_slice(&self.data[..max]);
|
||||||
|
self.data = &self.data[max..];
|
||||||
|
Ok(max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> io::Write for TestDevice<'a> {
|
||||||
|
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||||
|
assert!(bytes.len() == HID_RPT_SIZE + 1);
|
||||||
|
Ok(bytes.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> U2FDevice for TestDevice<'a> {
|
||||||
|
fn get_cid<'b>(&'b self) -> &'b [u8; 4] {
|
||||||
|
&self.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||||
|
self.cid = cid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
let mut dev = TestDevice::new(data);
|
||||||
|
let cmd = thread_rng().gen::<u8>();
|
||||||
|
let _ = sendrecv(&mut dev, cmd, data);
|
||||||
|
});
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
#[macro_use] extern crate libfuzzer_sys;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate u2fhid;
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::{cmp, io};
|
||||||
|
|
||||||
|
use u2fhid::{CID_BROADCAST, HID_RPT_SIZE};
|
||||||
|
use u2fhid::{U2FDevice, sendrecv};
|
||||||
|
|
||||||
|
struct TestDevice {
|
||||||
|
cid: [u8; 4],
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestDevice {
|
||||||
|
pub fn new() -> TestDevice {
|
||||||
|
TestDevice {
|
||||||
|
cid: CID_BROADCAST,
|
||||||
|
data: vec!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Read for TestDevice {
|
||||||
|
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
|
||||||
|
assert!(bytes.len() == HID_RPT_SIZE);
|
||||||
|
let max = cmp::min(self.data.len(), HID_RPT_SIZE);
|
||||||
|
bytes[..max].copy_from_slice(&self.data[..max]);
|
||||||
|
self.data = self.data[max..].to_vec();
|
||||||
|
Ok(max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for TestDevice {
|
||||||
|
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||||
|
assert!(bytes.len() == HID_RPT_SIZE + 1);
|
||||||
|
self.data.extend_from_slice(&bytes[1..]);
|
||||||
|
Ok(bytes.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U2FDevice for TestDevice {
|
||||||
|
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
|
||||||
|
&self.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||||
|
self.cid = cid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
let mut dev = TestDevice::new();
|
||||||
|
let cmd = thread_rng().gen::<u8>();
|
||||||
|
let res = sendrecv(&mut dev, cmd, data);
|
||||||
|
assert_eq!(data, &res.unwrap()[..]);
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
comment_width = 200
|
||||||
|
wrap_comments = true
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// No-op module to permit compiling token HID support for Android, where
|
||||||
|
// no results are returned.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use util::OnceCallback;
|
||||||
|
|
||||||
|
pub struct PlatformManager {}
|
||||||
|
|
||||||
|
impl PlatformManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
callback: OnceCallback<Vec<u8>>,
|
||||||
|
) {
|
||||||
|
// No-op on Android
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
key_handles: Vec<Vec<u8>>,
|
||||||
|
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||||
|
) {
|
||||||
|
// No-op on Android
|
||||||
|
}
|
||||||
|
|
||||||
|
// This blocks.
|
||||||
|
pub fn cancel(&mut self) {
|
||||||
|
// No-op on Android
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use libc::size_t;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::{ptr, slice};
|
||||||
|
|
||||||
|
use U2FManager;
|
||||||
|
|
||||||
|
type U2FKeyHandles = Vec<Vec<u8>>;
|
||||||
|
type U2FResult = HashMap<u8, Vec<u8>>;
|
||||||
|
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
|
||||||
|
|
||||||
|
const RESBUF_ID_REGISTRATION: u8 = 0;
|
||||||
|
const RESBUF_ID_KEYHANDLE: u8 = 1;
|
||||||
|
const RESBUF_ID_SIGNATURE: u8 = 2;
|
||||||
|
|
||||||
|
// Generates a new 64-bit transaction id with collision probability 2^-32.
|
||||||
|
fn new_tid() -> u64 {
|
||||||
|
thread_rng().gen::<u64>()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
|
||||||
|
slice::from_raw_parts(ptr, len).to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_u2f_mgr_new() -> *mut U2FManager {
|
||||||
|
if let Ok(mgr) = U2FManager::new() {
|
||||||
|
Box::into_raw(Box::new(mgr))
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut U2FManager) {
|
||||||
|
if !mgr.is_null() {
|
||||||
|
Box::from_raw(mgr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
|
||||||
|
Box::into_raw(Box::new(vec![]))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_khs_add(
|
||||||
|
khs: *mut U2FKeyHandles,
|
||||||
|
key_handle_ptr: *const u8,
|
||||||
|
key_handle_len: usize,
|
||||||
|
) {
|
||||||
|
(*khs).push(from_raw(key_handle_ptr, key_handle_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
|
||||||
|
if !khs.is_null() {
|
||||||
|
Box::from_raw(khs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_resbuf_length(
|
||||||
|
res: *const U2FResult,
|
||||||
|
bid: u8,
|
||||||
|
len: *mut size_t,
|
||||||
|
) -> bool {
|
||||||
|
if res.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(buf) = (*res).get(&bid) {
|
||||||
|
*len = buf.len();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_resbuf_copy(
|
||||||
|
res: *const U2FResult,
|
||||||
|
bid: u8,
|
||||||
|
dst: *mut u8,
|
||||||
|
) -> bool {
|
||||||
|
if res.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(buf) = (*res).get(&bid) {
|
||||||
|
ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) {
|
||||||
|
if !res.is_null() {
|
||||||
|
Box::from_raw(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_mgr_register(
|
||||||
|
mgr: *mut U2FManager,
|
||||||
|
timeout: u64,
|
||||||
|
callback: U2FCallback,
|
||||||
|
challenge_ptr: *const u8,
|
||||||
|
challenge_len: usize,
|
||||||
|
application_ptr: *const u8,
|
||||||
|
application_len: usize,
|
||||||
|
) -> u64 {
|
||||||
|
if mgr.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check buffers.
|
||||||
|
if challenge_ptr.is_null() || application_ptr.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let challenge = from_raw(challenge_ptr, challenge_len);
|
||||||
|
let application = from_raw(application_ptr, application_len);
|
||||||
|
|
||||||
|
let tid = new_tid();
|
||||||
|
let res = (*mgr).register(timeout, challenge, application, move |rv| {
|
||||||
|
if let Ok(registration) = rv {
|
||||||
|
let mut result = U2FResult::new();
|
||||||
|
result.insert(RESBUF_ID_REGISTRATION, registration);
|
||||||
|
callback(tid, Box::into_raw(Box::new(result)));
|
||||||
|
} else {
|
||||||
|
callback(tid, ptr::null_mut());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if res.is_ok() { tid } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
||||||
|
mgr: *mut U2FManager,
|
||||||
|
timeout: u64,
|
||||||
|
callback: U2FCallback,
|
||||||
|
challenge_ptr: *const u8,
|
||||||
|
challenge_len: usize,
|
||||||
|
application_ptr: *const u8,
|
||||||
|
application_len: usize,
|
||||||
|
khs: *const U2FKeyHandles,
|
||||||
|
) -> u64 {
|
||||||
|
if mgr.is_null() || khs.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check buffers.
|
||||||
|
if challenge_ptr.is_null() || application_ptr.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need at least one key handle.
|
||||||
|
if (*khs).len() < 1 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let challenge = from_raw(challenge_ptr, challenge_len);
|
||||||
|
let application = from_raw(application_ptr, application_len);
|
||||||
|
let key_handles = (*khs).clone();
|
||||||
|
|
||||||
|
let tid = new_tid();
|
||||||
|
let res = (*mgr).sign(timeout, challenge, application, key_handles, move |rv| {
|
||||||
|
if let Ok((key_handle, signature)) = rv {
|
||||||
|
let mut result = U2FResult::new();
|
||||||
|
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
|
||||||
|
result.insert(RESBUF_ID_SIGNATURE, signature);
|
||||||
|
callback(tid, Box::into_raw(Box::new(result)));
|
||||||
|
} else {
|
||||||
|
callback(tid, ptr::null_mut());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if res.is_ok() { tid } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut U2FManager) -> u64 {
|
||||||
|
if !mgr.is_null() {
|
||||||
|
// Ignore return value.
|
||||||
|
let _ = (*mgr).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
new_tid()
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// Allow dead code in this module, since it's all packet consts anyways.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub const HID_RPT_SIZE: usize = 64;
|
||||||
|
pub const U2FAPDUHEADER_SIZE: usize = 7;
|
||||||
|
pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
|
||||||
|
pub const TYPE_MASK: u8 = 0x80;
|
||||||
|
pub const TYPE_INIT: u8 = 0x80;
|
||||||
|
pub const TYPE_CONT: u8 = 0x80;
|
||||||
|
|
||||||
|
// Size of data chunk expected in U2F Init USB HID Packets
|
||||||
|
pub const INIT_DATA_SIZE: usize = HID_RPT_SIZE - 7;
|
||||||
|
// Size of data chunk expected in U2F Cont USB HID Packets
|
||||||
|
pub const CONT_DATA_SIZE: usize = HID_RPT_SIZE - 5;
|
||||||
|
|
||||||
|
pub const PARAMETER_SIZE: usize = 32;
|
||||||
|
|
||||||
|
pub const FIDO_USAGE_PAGE: u16 = 0xf1d0; // FIDO alliance HID usage page
|
||||||
|
pub const FIDO_USAGE_U2FHID: u16 = 0x01; // U2FHID usage for top-level collection
|
||||||
|
pub const FIDO_USAGE_DATA_IN: u8 = 0x20; // Raw IN data report
|
||||||
|
pub const FIDO_USAGE_DATA_OUT: u8 = 0x21; // Raw OUT data report
|
||||||
|
|
||||||
|
// General pub constants
|
||||||
|
|
||||||
|
pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation version
|
||||||
|
pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms
|
||||||
|
pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms
|
||||||
|
|
||||||
|
// U2FHID native commands
|
||||||
|
pub const U2FHID_PING: u8 = (TYPE_INIT | 0x01); // Echo data through local processor only
|
||||||
|
pub const U2FHID_MSG: u8 = (TYPE_INIT | 0x03); // Send U2F message frame
|
||||||
|
pub const U2FHID_LOCK: u8 = (TYPE_INIT | 0x04); // Send lock channel command
|
||||||
|
pub const U2FHID_INIT: u8 = (TYPE_INIT | 0x06); // Channel initialization
|
||||||
|
pub const U2FHID_WINK: u8 = (TYPE_INIT | 0x08); // Send device identification wink
|
||||||
|
pub const U2FHID_ERROR: u8 = (TYPE_INIT | 0x3f); // Error response
|
||||||
|
|
||||||
|
// U2FHID_MSG commands
|
||||||
|
pub const U2F_VENDOR_FIRST: u8 = (TYPE_INIT | 0x40); // First vendor defined command
|
||||||
|
pub const U2F_VENDOR_LAST: u8 = (TYPE_INIT | 0x7f); // Last vendor defined command
|
||||||
|
pub const U2F_REGISTER: u8 = 0x01; // Registration command
|
||||||
|
pub const U2F_AUTHENTICATE: u8 = 0x02; // Authenticate/sign command
|
||||||
|
pub const U2F_VERSION: u8 = 0x03; // Read version string command
|
||||||
|
|
||||||
|
// U2F_REGISTER command defines
|
||||||
|
pub const U2F_REGISTER_ID: u8 = 0x05; // Version 2 registration identifier
|
||||||
|
pub const U2F_REGISTER_HASH_ID: u8 = 0x00; // Version 2 hash identintifier
|
||||||
|
|
||||||
|
// U2F_AUTHENTICATE command defines
|
||||||
|
pub const U2F_REQUEST_USER_PRESENCE: u8 = 0x03; // Verify user presence and sign
|
||||||
|
pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is registered
|
||||||
|
|
||||||
|
// U2FHID_INIT command defines
|
||||||
|
pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge
|
||||||
|
pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command
|
||||||
|
pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command
|
||||||
|
|
||||||
|
// Low-level error codes. Return as negatives.
|
||||||
|
|
||||||
|
pub const ERR_NONE: u8 = 0x00; // No error
|
||||||
|
pub const ERR_INVALID_CMD: u8 = 0x01; // Invalid command
|
||||||
|
pub const ERR_INVALID_PAR: u8 = 0x02; // Invalid parameter
|
||||||
|
pub const ERR_INVALID_LEN: u8 = 0x03; // Invalid message length
|
||||||
|
pub const ERR_INVALID_SEQ: u8 = 0x04; // Invalid message sequencing
|
||||||
|
pub const ERR_MSG_TIMEOUT: u8 = 0x05; // Message has timed out
|
||||||
|
pub const ERR_CHANNEL_BUSY: u8 = 0x06; // Channel busy
|
||||||
|
pub const ERR_LOCK_REQUIRED: u8 = 0x0a; // Command requires channel lock
|
||||||
|
pub const ERR_INVALID_CID: u8 = 0x0b; // Command not allowed on this cid
|
||||||
|
pub const ERR_OTHER: u8 = 0x7f; // Other unspecified error
|
||||||
|
|
||||||
|
// These are ISO 7816-4 defined response status words.
|
||||||
|
pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00];
|
||||||
|
pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85];
|
||||||
|
pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80];
|
||||||
|
pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00];
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux"))]
|
||||||
|
extern crate libudev;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux"))]
|
||||||
|
#[path = "linux/mod.rs"]
|
||||||
|
pub mod platform;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos"))]
|
||||||
|
extern crate core_foundation_sys;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos"))]
|
||||||
|
#[path = "macos/mod.rs"]
|
||||||
|
pub mod platform;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows"))]
|
||||||
|
#[path = "windows/mod.rs"]
|
||||||
|
pub mod platform;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "android"))]
|
||||||
|
#[path = "android/mod.rs"]
|
||||||
|
pub mod platform;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate libc;
|
||||||
|
extern crate boxfnonce;
|
||||||
|
|
||||||
|
mod consts;
|
||||||
|
mod runloop;
|
||||||
|
mod u2ftypes;
|
||||||
|
mod u2fprotocol;
|
||||||
|
|
||||||
|
mod manager;
|
||||||
|
pub use manager::U2FManager;
|
||||||
|
|
||||||
|
mod capi;
|
||||||
|
pub use capi::*;
|
||||||
|
|
||||||
|
#[cfg(fuzzing)]
|
||||||
|
pub use u2fprotocol::*;
|
||||||
|
#[cfg(fuzzing)]
|
||||||
|
pub use u2ftypes::*;
|
||||||
|
#[cfg(fuzzing)]
|
||||||
|
pub use consts::*;
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use std::ffi::{CString, OsString};
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::os::unix::prelude::*;
|
||||||
|
|
||||||
|
use consts::CID_BROADCAST;
|
||||||
|
use platform::hidraw;
|
||||||
|
use util::{from_unix_result, to_io_err};
|
||||||
|
use u2ftypes::U2FDevice;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Device {
|
||||||
|
path: OsString,
|
||||||
|
fd: libc::c_int,
|
||||||
|
cid: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn new(path: OsString) -> io::Result<Self> {
|
||||||
|
let cstr = CString::new(path.as_bytes()).map_err(to_io_err)?;
|
||||||
|
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
|
||||||
|
let fd = from_unix_result(fd)?;
|
||||||
|
Ok(Self {
|
||||||
|
path,
|
||||||
|
fd,
|
||||||
|
cid: CID_BROADCAST,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_u2f(&self) -> bool {
|
||||||
|
hidraw::is_u2f_device(self.fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Device {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Close the fd, ignore any errors.
|
||||||
|
let _ = unsafe { libc::close(self.fd) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Device {
|
||||||
|
fn eq(&self, other: &Device) -> bool {
|
||||||
|
self.path == other.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Device {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
|
||||||
|
let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
|
||||||
|
from_unix_result(rv as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Device {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
let bufp = buf.as_ptr() as *const libc::c_void;
|
||||||
|
let rv = unsafe { libc::write(self.fd, bufp, buf.len()) };
|
||||||
|
from_unix_result(rv as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB HID writes don't buffer, so this will be a nop.
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U2FDevice for Device {
|
||||||
|
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
|
||||||
|
&self.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||||
|
self.cid = cid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::collections::hash_map::ValuesMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use platform::device::Device;
|
||||||
|
use platform::monitor::Event;
|
||||||
|
use u2fprotocol::u2f_init_device;
|
||||||
|
|
||||||
|
pub struct DeviceMap {
|
||||||
|
map: HashMap<OsString, Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceMap {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { map: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values_mut(&mut self) -> ValuesMut<OsString, Device> {
|
||||||
|
self.map.values_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_event(&mut self, event: Event) {
|
||||||
|
match event {
|
||||||
|
Event::Add(path) => self.add(path),
|
||||||
|
Event::Remove(path) => self.remove(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, path: OsString) {
|
||||||
|
if self.map.contains_key(&path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and try to open the device.
|
||||||
|
if let Ok(mut dev) = Device::new(path.clone()) {
|
||||||
|
if dev.is_u2f() && u2f_init_device(&mut dev) {
|
||||||
|
self.map.insert(path, dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, path: OsString) {
|
||||||
|
// Ignore errors.
|
||||||
|
let _ = self.map.remove(&path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
|
||||||
|
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
|
||||||
|
use util::{from_unix_result, io_err};
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ReportDescriptor {
|
||||||
|
size: ::libc::c_int,
|
||||||
|
value: [u8; 4096],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportDescriptor {
|
||||||
|
fn iter(self) -> ReportDescriptorIterator {
|
||||||
|
ReportDescriptorIterator::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NRBITS: u32 = 8;
|
||||||
|
const TYPEBITS: u32 = 8;
|
||||||
|
|
||||||
|
const READ: u8 = 2;
|
||||||
|
const SIZEBITS: u8 = 14;
|
||||||
|
|
||||||
|
const NRSHIFT: u32 = 0;
|
||||||
|
const TYPESHIFT: u32 = NRSHIFT + NRBITS as u32;
|
||||||
|
const SIZESHIFT: u32 = TYPESHIFT + TYPEBITS as u32;
|
||||||
|
const DIRSHIFT: u32 = SIZESHIFT + SIZEBITS as u32;
|
||||||
|
|
||||||
|
// The 4 MSBs (the tag) are set when it's a long item.
|
||||||
|
const HID_MASK_LONG_ITEM_TAG: u8 = 0b11110000;
|
||||||
|
// The 2 LSBs denote the size of a short item.
|
||||||
|
const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b00000011;
|
||||||
|
// The 6 MSBs denote the tag (4) and type (2).
|
||||||
|
const HID_MASK_ITEM_TAGTYPE: u8 = 0b11111100;
|
||||||
|
// tag=0000, type=10 (local)
|
||||||
|
const HID_ITEM_TAGTYPE_USAGE: u8 = 0b00001000;
|
||||||
|
// tag=0000, type=01 (global)
|
||||||
|
const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b00000100;
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/hid.h
|
||||||
|
const HID_MAX_DESCRIPTOR_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
macro_rules! ioctl {
|
||||||
|
($dir:expr, $name:ident, $ioty:expr, $nr:expr; $ty:ty) => (
|
||||||
|
pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
|
||||||
|
let size = mem::size_of::<$ty>();
|
||||||
|
let ioc = (($dir as u32) << DIRSHIFT) |
|
||||||
|
(($ioty as u32) << TYPESHIFT) |
|
||||||
|
(($nr as u32) << NRSHIFT) |
|
||||||
|
((size as u32) << SIZESHIFT);
|
||||||
|
from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/hidraw.h
|
||||||
|
ioctl!(READ, hidiocgrdescsize, b'H', 0x01; ::libc::c_int);
|
||||||
|
ioctl!(READ, hidiocgrdesc, b'H', 0x02; /*struct*/ ReportDescriptor);
|
||||||
|
|
||||||
|
enum Data {
|
||||||
|
UsagePage { data: u32 },
|
||||||
|
Usage { data: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReportDescriptorIterator {
|
||||||
|
desc: ReportDescriptor,
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportDescriptorIterator {
|
||||||
|
fn new(desc: ReportDescriptor) -> Self {
|
||||||
|
Self { desc, pos: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_item(&mut self) -> Option<Data> {
|
||||||
|
let item = get_hid_item(&self.desc.value[self.pos..]);
|
||||||
|
if item.is_none() {
|
||||||
|
self.pos = self.desc.size as usize; // Close, invalid data.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tag_type, key_len, data) = item.unwrap();
|
||||||
|
|
||||||
|
// Advance if we have a valid item.
|
||||||
|
self.pos += key_len + data.len();
|
||||||
|
|
||||||
|
// We only check short items.
|
||||||
|
if key_len > 1 {
|
||||||
|
return None; // Check next item.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short items have max. length of 4 bytes.
|
||||||
|
assert!(data.len() <= mem::size_of::<u32>());
|
||||||
|
|
||||||
|
// Convert data bytes to a uint.
|
||||||
|
let data = read_uint_le(data);
|
||||||
|
|
||||||
|
match tag_type {
|
||||||
|
HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
|
||||||
|
HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ReportDescriptorIterator {
|
||||||
|
type Item = Data;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.pos >= self.desc.size as usize {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next_item().or_else(|| self.next())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||||
|
if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
|
||||||
|
get_hid_long_item(buf)
|
||||||
|
} else {
|
||||||
|
get_hid_short_item(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||||
|
// A valid long item has at least three bytes.
|
||||||
|
if buf.len() < 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = buf[1] as usize;
|
||||||
|
|
||||||
|
// Ensure that there are enough bytes left in the buffer.
|
||||||
|
if len > buf.len() - 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((buf[2], 3 /* key length */, &buf[3..]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
|
||||||
|
// This is a short item. The bottom two bits of the key
|
||||||
|
// contain the length of the data section (value) for this key.
|
||||||
|
let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
|
||||||
|
s @ 0...2 => s as usize,
|
||||||
|
_ => 4, /* _ == 3 */
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure that there are enough bytes left in the buffer.
|
||||||
|
if len > buf.len() - 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((
|
||||||
|
buf[0] & HID_MASK_ITEM_TAGTYPE,
|
||||||
|
1, /* key length */
|
||||||
|
&buf[1..1 + len],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_uint_le(buf: &[u8]) -> u32 {
|
||||||
|
assert!(buf.len() <= 4);
|
||||||
|
// Parse the number in little endian byte order.
|
||||||
|
buf.iter().rev().fold(0, |num, b| (num << 8) | (*b as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_u2f_device(fd: RawFd) -> bool {
|
||||||
|
match read_report_descriptor(fd) {
|
||||||
|
Ok(desc) => has_fido_usage(desc),
|
||||||
|
Err(_) => false, // Upon failure, just say it's not a U2F device.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
|
||||||
|
let mut desc = ReportDescriptor {
|
||||||
|
size: 0,
|
||||||
|
value: [0; HID_MAX_DESCRIPTOR_SIZE],
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = unsafe { hidiocgrdescsize(fd, &mut desc.size)? };
|
||||||
|
if desc.size == 0 || desc.size as usize > desc.value.len() {
|
||||||
|
return Err(io_err("unexpected hidiocgrdescsize() result"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = unsafe { hidiocgrdesc(fd, &mut desc)? };
|
||||||
|
Ok(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_fido_usage(desc: ReportDescriptor) -> bool {
|
||||||
|
let mut usage_page = None;
|
||||||
|
let mut usage = None;
|
||||||
|
|
||||||
|
for data in desc.iter() {
|
||||||
|
match data {
|
||||||
|
Data::UsagePage { data } => usage_page = Some(data),
|
||||||
|
Data::Usage { data } => usage = Some(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the values we found.
|
||||||
|
if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
|
||||||
|
return usage_page == FIDO_USAGE_PAGE as u32 && usage == FIDO_USAGE_U2FHID as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
mod device;
|
||||||
|
mod devicemap;
|
||||||
|
mod hidraw;
|
||||||
|
mod monitor;
|
||||||
|
|
||||||
|
use consts::PARAMETER_SIZE;
|
||||||
|
use runloop::RunLoop;
|
||||||
|
use util::{io_err, OnceCallback};
|
||||||
|
use u2fprotocol::{u2f_is_keyhandle_valid, u2f_register, u2f_sign};
|
||||||
|
|
||||||
|
use self::devicemap::DeviceMap;
|
||||||
|
use self::monitor::Monitor;
|
||||||
|
|
||||||
|
pub struct PlatformManager {
|
||||||
|
// Handle to the thread loop.
|
||||||
|
thread: Option<RunLoop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { thread: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
callback: OnceCallback<Vec<u8>>,
|
||||||
|
) {
|
||||||
|
// Abort any prior register/sign calls.
|
||||||
|
self.cancel();
|
||||||
|
|
||||||
|
let cbc = callback.clone();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut devices = DeviceMap::new();
|
||||||
|
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
|
||||||
|
|
||||||
|
while alive() && monitor.alive() {
|
||||||
|
// Add/remove devices.
|
||||||
|
for event in monitor.events() {
|
||||||
|
devices.process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to register each device.
|
||||||
|
for device in devices.values_mut() {
|
||||||
|
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
|
||||||
|
callback.call(Ok(bytes));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a little before trying again.
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(Err(io_err("aborted or timed out")));
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.thread = Some(try_or!(
|
||||||
|
thread,
|
||||||
|
|_| cbc.call(Err(io_err("couldn't create runloop")))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
key_handles: Vec<Vec<u8>>,
|
||||||
|
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||||
|
) {
|
||||||
|
// Abort any prior register/sign calls.
|
||||||
|
self.cancel();
|
||||||
|
|
||||||
|
let cbc = callback.clone();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut devices = DeviceMap::new();
|
||||||
|
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
|
||||||
|
|
||||||
|
while alive() && monitor.alive() {
|
||||||
|
// Add/remove devices.
|
||||||
|
for event in monitor.events() {
|
||||||
|
devices.process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try signing with each device.
|
||||||
|
for key_handle in &key_handles {
|
||||||
|
for device in devices.values_mut() {
|
||||||
|
// Check if they key handle belongs to the current device.
|
||||||
|
let is_valid = match u2f_is_keyhandle_valid(
|
||||||
|
device,
|
||||||
|
&challenge,
|
||||||
|
&application,
|
||||||
|
key_handle,
|
||||||
|
) {
|
||||||
|
Ok(valid) => valid,
|
||||||
|
Err(_) => continue, // Skip this device for now.
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
// If yes, try to sign.
|
||||||
|
if let Ok(bytes) = u2f_sign(
|
||||||
|
device,
|
||||||
|
&challenge,
|
||||||
|
&application,
|
||||||
|
key_handle,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
callback.call(Ok((key_handle.clone(), bytes)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no, keep registering and blinking with bogus data
|
||||||
|
let blank = vec![0u8; PARAMETER_SIZE];
|
||||||
|
if let Ok(_) = u2f_register(device, &blank, &blank) {
|
||||||
|
callback.call(Err(io_err("invalid key")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a little before trying again.
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(Err(io_err("aborted or timed out")));
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.thread = Some(try_or!(
|
||||||
|
thread,
|
||||||
|
|_| cbc.call(Err(io_err("couldn't create runloop")))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This blocks.
|
||||||
|
pub fn cancel(&mut self) {
|
||||||
|
if let Some(thread) = self.thread.take() {
|
||||||
|
thread.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use libudev;
|
||||||
|
use libudev::EventType;
|
||||||
|
|
||||||
|
use libc::{c_int, c_short, c_ulong};
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::io;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::sync::mpsc::{channel, Receiver, TryIter};
|
||||||
|
|
||||||
|
use runloop::RunLoop;
|
||||||
|
use util::to_io_err;
|
||||||
|
|
||||||
|
const UDEV_SUBSYSTEM: &'static str = "hidraw";
|
||||||
|
const POLLIN: c_short = 0x0001;
|
||||||
|
const POLL_TIMEOUT: c_int = 100;
|
||||||
|
|
||||||
|
fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> {
|
||||||
|
let nfds = fds.len() as c_ulong;
|
||||||
|
|
||||||
|
let rv = unsafe { ::libc::poll((&mut fds[..]).as_mut_ptr(), nfds, POLL_TIMEOUT) };
|
||||||
|
|
||||||
|
if rv < 0 {
|
||||||
|
Err(io::Error::from_raw_os_error(rv))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Add(OsString),
|
||||||
|
Remove(OsString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
fn from_udev(event: libudev::Event) -> Option<Self> {
|
||||||
|
let path = event.device().devnode().map(
|
||||||
|
|dn| dn.to_owned().into_os_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match (event.event_type(), path) {
|
||||||
|
(EventType::Add, Some(path)) => Some(Event::Add(path)),
|
||||||
|
(EventType::Remove, Some(path)) => Some(Event::Remove(path)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Monitor {
|
||||||
|
// Receive events from the thread.
|
||||||
|
rx: Receiver<Event>,
|
||||||
|
// Handle to the thread loop.
|
||||||
|
thread: RunLoop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monitor {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| -> io::Result<()> {
|
||||||
|
let ctx = libudev::Context::new()?;
|
||||||
|
let mut enumerator = libudev::Enumerator::new(&ctx)?;
|
||||||
|
enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
|
||||||
|
|
||||||
|
// Iterate all existing devices.
|
||||||
|
for dev in enumerator.scan_devices()? {
|
||||||
|
if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) {
|
||||||
|
tx.send(Event::Add(path)).map_err(to_io_err)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut monitor = libudev::Monitor::new(&ctx)?;
|
||||||
|
monitor.match_subsystem(UDEV_SUBSYSTEM)?;
|
||||||
|
|
||||||
|
// Start listening for new devices.
|
||||||
|
let mut socket = monitor.listen()?;
|
||||||
|
let mut fds = vec![
|
||||||
|
::libc::pollfd {
|
||||||
|
fd: socket.as_raw_fd(),
|
||||||
|
events: POLLIN,
|
||||||
|
revents: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Loop until we're stopped by the controlling thread, or fail.
|
||||||
|
while alive() {
|
||||||
|
// Wait for new events, break on failure.
|
||||||
|
poll(&mut fds)?;
|
||||||
|
|
||||||
|
// Send the event over.
|
||||||
|
let udev_event = socket.receive_event();
|
||||||
|
if let Some(event) = udev_event.and_then(Event::from_udev) {
|
||||||
|
tx.send(event).map_err(to_io_err)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
0, /* no timeout */
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self { rx, thread })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events<'a>(&'a self) -> TryIter<'a, Event> {
|
||||||
|
self.rx.try_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
self.thread.alive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Monitor {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.thread.cancel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::slice;
|
||||||
|
use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use core_foundation_sys::base::*;
|
||||||
|
use libc::c_void;
|
||||||
|
|
||||||
|
use consts::{CID_BROADCAST, HID_RPT_SIZE};
|
||||||
|
use u2ftypes::U2FDevice;
|
||||||
|
|
||||||
|
use super::iokit::*;
|
||||||
|
|
||||||
|
const READ_TIMEOUT: u64 = 15;
|
||||||
|
|
||||||
|
pub struct Device {
|
||||||
|
device_ref: IOHIDDeviceRef,
|
||||||
|
cid: [u8; 4],
|
||||||
|
report_rx: Receiver<Vec<u8>>,
|
||||||
|
report_send_void: *mut c_void,
|
||||||
|
scratch_buf_ptr: *mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn new(device_ref: IOHIDDeviceRef) -> Self {
|
||||||
|
let (report_tx, report_rx) = channel();
|
||||||
|
let report_send_void = Box::into_raw(Box::new(report_tx)) as *mut c_void;
|
||||||
|
|
||||||
|
let scratch_buf = [0; HID_RPT_SIZE];
|
||||||
|
let scratch_buf_ptr = Box::into_raw(Box::new(scratch_buf)) as *mut u8;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
IOHIDDeviceRegisterInputReportCallback(
|
||||||
|
device_ref,
|
||||||
|
scratch_buf_ptr,
|
||||||
|
HID_RPT_SIZE as CFIndex,
|
||||||
|
read_new_data_cb,
|
||||||
|
report_send_void,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
device_ref,
|
||||||
|
cid: CID_BROADCAST,
|
||||||
|
report_rx,
|
||||||
|
report_send_void,
|
||||||
|
scratch_buf_ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Device {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
debug!("Dropping U2F device {}", self);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Re-allocate raw pointers for destruction.
|
||||||
|
let _ = Box::from_raw(self.report_send_void as *mut Sender<Vec<u8>>);
|
||||||
|
let _ = Box::from_raw(self.scratch_buf_ptr as *mut [u8; HID_RPT_SIZE]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Device {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"InternalDevice(ref:{:?}, cid: {:02x}{:02x}{:02x}{:02x})",
|
||||||
|
self.device_ref,
|
||||||
|
self.cid[0],
|
||||||
|
self.cid[1],
|
||||||
|
self.cid[2],
|
||||||
|
self.cid[3]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Device {
|
||||||
|
fn eq(&self, other_device: &Device) -> bool {
|
||||||
|
self.device_ref == other_device.device_ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Device {
|
||||||
|
fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let timeout = Duration::from_secs(READ_TIMEOUT);
|
||||||
|
let data = match self.report_rx.recv_timeout(timeout) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) if e == RecvTimeoutError::Timeout => {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::TimedOut, e));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bytes.write(&data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Device {
|
||||||
|
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||||
|
assert_eq!(bytes.len(), HID_RPT_SIZE + 1);
|
||||||
|
|
||||||
|
let report_id = bytes[0] as i64;
|
||||||
|
// Skip report number when not using numbered reports.
|
||||||
|
let start = if report_id == 0x0 { 1 } else { 0 };
|
||||||
|
let data = &bytes[start..];
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
IOHIDDeviceSetReport(
|
||||||
|
self.device_ref,
|
||||||
|
kIOHIDReportTypeOutput,
|
||||||
|
report_id,
|
||||||
|
data.as_ptr(),
|
||||||
|
data.len() as CFIndex,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if result != 0 {
|
||||||
|
warn!("set_report sending failure = {0:X}", result);
|
||||||
|
return Err(io::Error::from_raw_os_error(result));
|
||||||
|
}
|
||||||
|
trace!("set_report sending success = {0:X}", result);
|
||||||
|
|
||||||
|
Ok(bytes.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB HID writes don't buffer, so this will be a nop.
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U2FDevice for Device {
|
||||||
|
fn get_cid(&self) -> &[u8; 4] {
|
||||||
|
&self.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||||
|
self.cid = cid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called from the RunLoop thread
|
||||||
|
extern "C" fn read_new_data_cb(
|
||||||
|
context: *mut c_void,
|
||||||
|
_: IOReturn,
|
||||||
|
_: *mut c_void,
|
||||||
|
report_type: IOHIDReportType,
|
||||||
|
report_id: u32,
|
||||||
|
report: *mut u8,
|
||||||
|
report_len: CFIndex,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let tx = &mut *(context as *mut Sender<Vec<u8>>);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"read_new_data_cb type={} id={} report={:?} len={}",
|
||||||
|
report_type,
|
||||||
|
report_id,
|
||||||
|
report,
|
||||||
|
report_len
|
||||||
|
);
|
||||||
|
|
||||||
|
let report_len = report_len as usize;
|
||||||
|
if report_len > HID_RPT_SIZE {
|
||||||
|
warn!(
|
||||||
|
"read_new_data_cb got too much data! {} > {}",
|
||||||
|
report_len,
|
||||||
|
HID_RPT_SIZE
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = slice::from_raw_parts(report, report_len).to_vec();
|
||||||
|
|
||||||
|
if let Err(e) = tx.send(data) {
|
||||||
|
// TOOD: This happens when the channel closes before this thread
|
||||||
|
// does. This is pretty common, but let's deal with stopping
|
||||||
|
// properly later.
|
||||||
|
warn!("Problem returning read_new_data_cb data for thread: {}", e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::collections::hash_map::ValuesMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use u2fprotocol::u2f_init_device;
|
||||||
|
|
||||||
|
use platform::monitor::Event;
|
||||||
|
use platform::device::Device;
|
||||||
|
use platform::iokit::*;
|
||||||
|
|
||||||
|
pub struct DeviceMap {
|
||||||
|
map: HashMap<IOHIDDeviceRef, Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceMap {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { map: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values_mut(&mut self) -> ValuesMut<IOHIDDeviceRef, Device> {
|
||||||
|
self.map.values_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_event(&mut self, event: Event) {
|
||||||
|
match event {
|
||||||
|
Event::Add(dev) => self.add(dev),
|
||||||
|
Event::Remove(dev) => self.remove(dev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, device_ref: IOHIDDeviceRef) {
|
||||||
|
if self.map.contains_key(&device_ref) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the device.
|
||||||
|
let mut dev = Device::new(device_ref);
|
||||||
|
|
||||||
|
if u2f_init_device(&mut dev) {
|
||||||
|
self.map.insert(device_ref, dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, device_ref: IOHIDDeviceRef) {
|
||||||
|
// Ignore errors.
|
||||||
|
let _ = self.map.remove(&device_ref);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate log;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use super::iokit::*;
|
||||||
|
use core_foundation_sys::base::*;
|
||||||
|
use core_foundation_sys::dictionary::*;
|
||||||
|
use core_foundation_sys::number::*;
|
||||||
|
use core_foundation_sys::runloop::*;
|
||||||
|
use core_foundation_sys::string::*;
|
||||||
|
|
||||||
|
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
|
||||||
|
use util::io_err;
|
||||||
|
|
||||||
|
pub struct IOHIDDeviceMatcher {
|
||||||
|
dict: CFDictionaryRef,
|
||||||
|
keys: Vec<CFStringRef>,
|
||||||
|
values: Vec<CFNumberRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IOHIDDeviceMatcher {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let keys = vec![
|
||||||
|
IOHIDDeviceMatcher::cf_string("DeviceUsage"),
|
||||||
|
IOHIDDeviceMatcher::cf_string("DeviceUsagePage"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let values = vec![
|
||||||
|
IOHIDDeviceMatcher::cf_number(FIDO_USAGE_U2FHID as i32),
|
||||||
|
IOHIDDeviceMatcher::cf_number(FIDO_USAGE_PAGE as i32),
|
||||||
|
];
|
||||||
|
|
||||||
|
let dict = unsafe {
|
||||||
|
CFDictionaryCreate(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
keys.as_ptr() as *const *const libc::c_void,
|
||||||
|
values.as_ptr() as *const *const libc::c_void,
|
||||||
|
keys.len() as CFIndex,
|
||||||
|
&kCFTypeDictionaryKeyCallBacks,
|
||||||
|
&kCFTypeDictionaryValueCallBacks,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { dict, keys, values }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cf_number(number: i32) -> CFNumberRef {
|
||||||
|
let nbox = Box::new(number);
|
||||||
|
let nptr = Box::into_raw(nbox) as *mut libc::c_void;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Drop when out of scope.
|
||||||
|
let _num = Box::from_raw(nptr as *mut i32);
|
||||||
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, nptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cf_string(string: &str) -> CFStringRef {
|
||||||
|
unsafe {
|
||||||
|
CFStringCreateWithBytes(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
string.as_ptr(),
|
||||||
|
string.len() as CFIndex,
|
||||||
|
kCFStringEncodingUTF8,
|
||||||
|
false as Boolean,
|
||||||
|
kCFAllocatorNull,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> CFDictionaryRef {
|
||||||
|
self.dict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for IOHIDDeviceMatcher {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CFRelease(self.dict as *mut libc::c_void) };
|
||||||
|
|
||||||
|
for key in &self.keys {
|
||||||
|
unsafe { CFRelease(*key as *mut libc::c_void) };
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in &self.values {
|
||||||
|
unsafe { CFRelease(*value as *mut libc::c_void) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IOHIDManager {
|
||||||
|
manager: IOHIDManagerRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IOHIDManager {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
|
||||||
|
|
||||||
|
let rv = unsafe { IOHIDManagerOpen(manager, kIOHIDManagerOptionNone) };
|
||||||
|
if rv != 0 {
|
||||||
|
return Err(io_err("Couldn't open HID Manager"));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { manager })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> IOHIDManagerRef {
|
||||||
|
self.manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for IOHIDManager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let rv = unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
|
||||||
|
if rv != 0 {
|
||||||
|
warn!("Couldn't close the HID Manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
|
||||||
|
|
||||||
|
extern crate core_foundation_sys;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use libc::c_void;
|
||||||
|
use core_foundation_sys::base::{CFIndex, CFAllocatorRef};
|
||||||
|
use core_foundation_sys::string::CFStringRef;
|
||||||
|
use core_foundation_sys::runloop::CFRunLoopRef;
|
||||||
|
use core_foundation_sys::dictionary::CFDictionaryRef;
|
||||||
|
|
||||||
|
type IOOptionBits = u32;
|
||||||
|
|
||||||
|
pub type IOReturn = libc::c_int;
|
||||||
|
|
||||||
|
pub type IOHIDManagerRef = *mut __IOHIDManager;
|
||||||
|
pub type IOHIDManagerOptions = IOOptionBits;
|
||||||
|
|
||||||
|
pub type IOHIDDeviceCallback = extern "C" fn(context: *mut c_void,
|
||||||
|
result: IOReturn,
|
||||||
|
sender: *mut c_void,
|
||||||
|
device: IOHIDDeviceRef);
|
||||||
|
|
||||||
|
pub type IOHIDReportType = IOOptionBits;
|
||||||
|
pub type IOHIDReportCallback = extern "C" fn(context: *mut c_void,
|
||||||
|
result: IOReturn,
|
||||||
|
sender: *mut c_void,
|
||||||
|
report_type: IOHIDReportType,
|
||||||
|
report_id: u32,
|
||||||
|
report: *mut u8,
|
||||||
|
report_len: CFIndex);
|
||||||
|
|
||||||
|
pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0;
|
||||||
|
|
||||||
|
pub const kIOHIDReportTypeOutput: IOHIDReportType = 1;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct __IOHIDManager {
|
||||||
|
__private: c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct IOHIDDeviceRef(*const c_void);
|
||||||
|
|
||||||
|
unsafe impl Send for IOHIDDeviceRef {}
|
||||||
|
unsafe impl Sync for IOHIDDeviceRef {}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
// IOHIDManager
|
||||||
|
pub fn IOHIDManagerCreate(
|
||||||
|
allocator: CFAllocatorRef,
|
||||||
|
options: IOHIDManagerOptions,
|
||||||
|
) -> IOHIDManagerRef;
|
||||||
|
pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
|
||||||
|
pub fn IOHIDManagerRegisterDeviceMatchingCallback(
|
||||||
|
manager: IOHIDManagerRef,
|
||||||
|
callback: IOHIDDeviceCallback,
|
||||||
|
context: *mut c_void,
|
||||||
|
);
|
||||||
|
pub fn IOHIDManagerRegisterDeviceRemovalCallback(
|
||||||
|
manager: IOHIDManagerRef,
|
||||||
|
callback: IOHIDDeviceCallback,
|
||||||
|
context: *mut c_void,
|
||||||
|
);
|
||||||
|
pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
|
||||||
|
pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
|
||||||
|
pub fn IOHIDManagerScheduleWithRunLoop(
|
||||||
|
manager: IOHIDManagerRef,
|
||||||
|
runLoop: CFRunLoopRef,
|
||||||
|
runLoopMode: CFStringRef,
|
||||||
|
);
|
||||||
|
|
||||||
|
// IOHIDDevice
|
||||||
|
pub fn IOHIDDeviceSetReport(
|
||||||
|
device: IOHIDDeviceRef,
|
||||||
|
reportType: IOHIDReportType,
|
||||||
|
reportID: CFIndex,
|
||||||
|
report: *const u8,
|
||||||
|
reportLength: CFIndex,
|
||||||
|
) -> IOReturn;
|
||||||
|
pub fn IOHIDDeviceRegisterInputReportCallback(
|
||||||
|
device: IOHIDDeviceRef,
|
||||||
|
report: *const u8,
|
||||||
|
reportLength: CFIndex,
|
||||||
|
callback: IOHIDReportCallback,
|
||||||
|
context: *mut c_void,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate log;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
mod device;
|
||||||
|
mod devicemap;
|
||||||
|
mod iokit;
|
||||||
|
mod iohid;
|
||||||
|
mod monitor;
|
||||||
|
|
||||||
|
use self::devicemap::DeviceMap;
|
||||||
|
use self::monitor::Monitor;
|
||||||
|
|
||||||
|
use consts::PARAMETER_SIZE;
|
||||||
|
use runloop::RunLoop;
|
||||||
|
use util::{io_err, OnceCallback};
|
||||||
|
use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PlatformManager {
|
||||||
|
// Handle to the thread loop.
|
||||||
|
thread: Option<RunLoop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
callback: OnceCallback<Vec<u8>>,
|
||||||
|
) {
|
||||||
|
// Abort any prior register/sign calls.
|
||||||
|
self.cancel();
|
||||||
|
|
||||||
|
let cbc = callback.clone();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut devices = DeviceMap::new();
|
||||||
|
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
|
||||||
|
|
||||||
|
'top: while alive() && monitor.alive() {
|
||||||
|
for event in monitor.events() {
|
||||||
|
devices.process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
for device in devices.values_mut() {
|
||||||
|
// Caller asked us to register, so the first token that does wins
|
||||||
|
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
|
||||||
|
callback.call(Ok(bytes));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if monitor.events has any hotplug events that we'll need
|
||||||
|
// to handle
|
||||||
|
if monitor.events().size_hint().0 > 0 {
|
||||||
|
debug!("Hotplug event; restarting loop");
|
||||||
|
continue 'top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(Err(io_err("aborted or timed out")));
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.thread = Some(try_or!(
|
||||||
|
thread,
|
||||||
|
|_| cbc.call(Err(io_err("couldn't create runloop")))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn sign(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
key_handles: Vec<Vec<u8>>,
|
||||||
|
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||||
|
) {
|
||||||
|
// Abort any prior register/sign calls.
|
||||||
|
self.cancel();
|
||||||
|
|
||||||
|
let cbc = callback.clone();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut devices = DeviceMap::new();
|
||||||
|
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
|
||||||
|
|
||||||
|
'top: while alive() && monitor.alive() {
|
||||||
|
for event in monitor.events() {
|
||||||
|
devices.process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
for key_handle in &key_handles {
|
||||||
|
for device in devices.values_mut() {
|
||||||
|
// Determine if this key handle belongs to this token
|
||||||
|
let is_valid = match u2f_is_keyhandle_valid(
|
||||||
|
device,
|
||||||
|
&challenge,
|
||||||
|
&application,
|
||||||
|
key_handle,
|
||||||
|
) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => continue, // Skip this device for now.
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
// It does, we can sign
|
||||||
|
if let Ok(bytes) = u2f_sign(
|
||||||
|
device,
|
||||||
|
&challenge,
|
||||||
|
&application,
|
||||||
|
key_handle,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
callback.call(Ok((key_handle.clone(), bytes)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If doesn't, so blink anyway (using bogus data)
|
||||||
|
let blank = vec![0u8; PARAMETER_SIZE];
|
||||||
|
|
||||||
|
if u2f_register(device, &blank, &blank).is_ok() {
|
||||||
|
// If the user selects this token that can't satisfy, it's an
|
||||||
|
// error
|
||||||
|
callback.call(Err(io_err("invalid key")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if monitor.events has any hotplug events that we'll
|
||||||
|
// need to handle
|
||||||
|
if monitor.events().size_hint().0 > 0 {
|
||||||
|
debug!("Hotplug event; restarting loop");
|
||||||
|
continue 'top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(Err(io_err("aborted or timed out")));
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.thread = Some(try_or!(
|
||||||
|
thread,
|
||||||
|
|_| cbc.call(Err(io_err("couldn't create runloop")))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(&mut self) {
|
||||||
|
if let Some(thread) = self.thread.take() {
|
||||||
|
thread.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for PlatformManager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
debug!("OSX PlatformManager dropped");
|
||||||
|
self.cancel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::sync::mpsc::{channel, Sender, Receiver, TryIter};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use super::iohid::*;
|
||||||
|
use super::iokit::*;
|
||||||
|
use core_foundation_sys::runloop::*;
|
||||||
|
use runloop::RunLoop;
|
||||||
|
|
||||||
|
extern crate log;
|
||||||
|
extern crate libc;
|
||||||
|
use libc::c_void;
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Add(IOHIDDeviceRef),
|
||||||
|
Remove(IOHIDDeviceRef),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Monitor {
|
||||||
|
// Receive events from the thread.
|
||||||
|
rx: Receiver<Event>,
|
||||||
|
// Handle to the thread loop.
|
||||||
|
thread: RunLoop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monitor {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| -> io::Result<()> {
|
||||||
|
let tx_box = Box::new(tx);
|
||||||
|
let tx_ptr = Box::into_raw(tx_box) as *mut libc::c_void;
|
||||||
|
|
||||||
|
// This will keep `tx` alive only for the scope.
|
||||||
|
let _tx = unsafe { Box::from_raw(tx_ptr as *mut Sender<Event>) };
|
||||||
|
|
||||||
|
// Create and initialize a scoped HID manager.
|
||||||
|
let manager = IOHIDManager::new()?;
|
||||||
|
|
||||||
|
// Match only U2F devices.
|
||||||
|
let dict = IOHIDDeviceMatcher::new();
|
||||||
|
unsafe { IOHIDManagerSetDeviceMatching(manager.get(), dict.get()) };
|
||||||
|
|
||||||
|
// Register callbacks.
|
||||||
|
unsafe {
|
||||||
|
IOHIDManagerRegisterDeviceMatchingCallback(
|
||||||
|
manager.get(),
|
||||||
|
Monitor::device_add_cb,
|
||||||
|
tx_ptr,
|
||||||
|
);
|
||||||
|
IOHIDManagerRegisterDeviceRemovalCallback(
|
||||||
|
manager.get(),
|
||||||
|
Monitor::device_remove_cb,
|
||||||
|
tx_ptr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the Event Loop. CFRunLoopRunInMode() will dispatch HID
|
||||||
|
// input reports into the various callbacks
|
||||||
|
while alive() {
|
||||||
|
trace!("OSX Runloop running, handle={:?}", thread::current());
|
||||||
|
|
||||||
|
if unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, 0) } ==
|
||||||
|
kCFRunLoopRunStopped
|
||||||
|
{
|
||||||
|
debug!("OSX Runloop device stopped.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("OSX Runloop completed, handle={:?}", thread::current());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
0, /* no timeout */
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self { rx, thread })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events(&self) -> TryIter<Event> {
|
||||||
|
self.rx.try_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
self.thread.alive()
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn device_add_cb(
|
||||||
|
context: *mut c_void,
|
||||||
|
_: IOReturn,
|
||||||
|
_: *mut c_void,
|
||||||
|
device: IOHIDDeviceRef,
|
||||||
|
) {
|
||||||
|
let tx = unsafe { &*(context as *mut Sender<Event>) };
|
||||||
|
let _ = tx.send(Event::Add(device));
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn device_remove_cb(
|
||||||
|
context: *mut c_void,
|
||||||
|
_: IOReturn,
|
||||||
|
_: *mut c_void,
|
||||||
|
device: IOHIDDeviceRef,
|
||||||
|
) {
|
||||||
|
let tx = unsafe { &*(context as *mut Sender<Event>) };
|
||||||
|
let _ = tx.send(Event::Remove(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Monitor {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
debug!("OSX Runloop dropped");
|
||||||
|
self.thread.cancel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::sync::mpsc::{channel, Sender, RecvTimeoutError};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use consts::PARAMETER_SIZE;
|
||||||
|
use platform::PlatformManager;
|
||||||
|
use runloop::RunLoop;
|
||||||
|
use util::{to_io_err, OnceCallback};
|
||||||
|
|
||||||
|
pub enum QueueAction {
|
||||||
|
Register {
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
callback: OnceCallback<Vec<u8>>,
|
||||||
|
},
|
||||||
|
Sign {
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
key_handles: Vec<Vec<u8>>,
|
||||||
|
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||||
|
},
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct U2FManager {
|
||||||
|
queue: RunLoop,
|
||||||
|
tx: Sender<QueueAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U2FManager {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
// Start a new work queue thread.
|
||||||
|
let queue = try!(RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut pm = PlatformManager::new();
|
||||||
|
|
||||||
|
while alive() {
|
||||||
|
match rx.recv_timeout(Duration::from_millis(50)) {
|
||||||
|
Ok(QueueAction::Register {
|
||||||
|
timeout,
|
||||||
|
challenge,
|
||||||
|
application,
|
||||||
|
callback,
|
||||||
|
}) => {
|
||||||
|
// This must not block, otherwise we can't cancel.
|
||||||
|
pm.register(timeout, challenge, application, callback);
|
||||||
|
}
|
||||||
|
Ok(QueueAction::Sign {
|
||||||
|
timeout,
|
||||||
|
challenge,
|
||||||
|
application,
|
||||||
|
key_handles,
|
||||||
|
callback,
|
||||||
|
}) => {
|
||||||
|
// This must not block, otherwise we can't cancel.
|
||||||
|
pm.sign(timeout, challenge, application, key_handles, callback);
|
||||||
|
}
|
||||||
|
Ok(QueueAction::Cancel) => {
|
||||||
|
// Cancelling must block so that we don't start a new
|
||||||
|
// polling thread before the old one has shut down.
|
||||||
|
pm.cancel();
|
||||||
|
}
|
||||||
|
Err(RecvTimeoutError::Disconnected) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => { /* continue */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel any ongoing activity.
|
||||||
|
pm.cancel();
|
||||||
|
},
|
||||||
|
0, /* no timeout */
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
queue: queue,
|
||||||
|
tx: tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register<F>(
|
||||||
|
&self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
callback: F,
|
||||||
|
) -> io::Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce(io::Result<Vec<u8>>),
|
||||||
|
F: Send + 'static,
|
||||||
|
{
|
||||||
|
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid parameter sizes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback = OnceCallback::new(callback);
|
||||||
|
let action = QueueAction::Register {
|
||||||
|
timeout: timeout,
|
||||||
|
challenge: challenge,
|
||||||
|
application: application,
|
||||||
|
callback: callback,
|
||||||
|
};
|
||||||
|
self.tx.send(action).map_err(to_io_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign<F>(
|
||||||
|
&self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
key_handles: Vec<Vec<u8>>,
|
||||||
|
callback: F,
|
||||||
|
) -> io::Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce(io::Result<(Vec<u8>, Vec<u8>)>),
|
||||||
|
F: Send + 'static,
|
||||||
|
{
|
||||||
|
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid parameter sizes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if key_handles.len() < 1 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"No key handles given",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for key_handle in &key_handles {
|
||||||
|
if key_handle.len() > 256 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Key handle too large",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback = OnceCallback::new(callback);
|
||||||
|
let action = QueueAction::Sign {
|
||||||
|
timeout: timeout,
|
||||||
|
challenge: challenge,
|
||||||
|
application: application,
|
||||||
|
key_handles: key_handles,
|
||||||
|
callback: callback,
|
||||||
|
};
|
||||||
|
self.tx.send(action).map_err(to_io_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(&self) -> io::Result<()> {
|
||||||
|
self.tx.send(QueueAction::Cancel).map_err(to_io_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for U2FManager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.queue.cancel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::thread;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
struct Canary {
|
||||||
|
alive: AtomicBool,
|
||||||
|
thread: Mutex<Option<JoinHandle<()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Canary {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
alive: AtomicBool::new(true),
|
||||||
|
thread: Mutex::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RunLoop {
|
||||||
|
flag: Weak<Canary>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunLoop {
|
||||||
|
pub fn new<F, T>(fun: F, timeout_ms: u64) -> io::Result<Self>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Fn() -> bool) -> T,
|
||||||
|
F: Send + 'static,
|
||||||
|
{
|
||||||
|
let flag = Arc::new(Canary::new());
|
||||||
|
let flag_ = flag.clone();
|
||||||
|
|
||||||
|
// Spawn the run loop thread.
|
||||||
|
let thread = thread::Builder::new().spawn(move || {
|
||||||
|
let timeout = Duration::from_millis(timeout_ms);
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
// A callback to determine whether the thread should terminate.
|
||||||
|
let still_alive = || {
|
||||||
|
// `flag.alive` will be false after cancel() was called.
|
||||||
|
flag.alive.load(Ordering::Relaxed) &&
|
||||||
|
// If a timeout was provided, we'll check that too.
|
||||||
|
(timeout_ms == 0 || start.elapsed() < timeout)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ignore return values.
|
||||||
|
let _ = fun(&still_alive);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// We really should never fail to lock here.
|
||||||
|
let mut guard = (*flag_).thread.lock().map_err(|_| {
|
||||||
|
io::Error::new(io::ErrorKind::Other, "failed to lock")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Store the thread handle so we can join later.
|
||||||
|
*guard = Some(thread);
|
||||||
|
|
||||||
|
Ok(Self { flag: Arc::downgrade(&flag_) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancels the run loop and waits for the thread to terminate.
|
||||||
|
// This is a potentially BLOCKING operation.
|
||||||
|
pub fn cancel(&self) {
|
||||||
|
// If the thread still exists...
|
||||||
|
if let Some(flag) = self.flag.upgrade() {
|
||||||
|
// ...let the run loop terminate.
|
||||||
|
flag.alive.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
|
// Locking should never fail here either.
|
||||||
|
if let Ok(mut guard) = flag.thread.lock() {
|
||||||
|
// This really can't fail.
|
||||||
|
if let Some(handle) = (*guard).take() {
|
||||||
|
// This might fail, ignore.
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tells whether the runloop is alive.
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
// If the thread still exists...
|
||||||
|
if let Some(flag) = self.flag.upgrade() {
|
||||||
|
flag.alive.load(Ordering::Relaxed)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
|
use super::RunLoop;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty() {
|
||||||
|
// Create a runloop that exits right away.
|
||||||
|
let thread = RunLoop::new(move |_| {}, 0).unwrap();
|
||||||
|
while thread.alive() { /* wait */ }
|
||||||
|
thread.cancel(); // noop
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cancel_early() {
|
||||||
|
// Create a runloop and cancel it before the thread spawns.
|
||||||
|
RunLoop::new(|alive| assert!(!alive()), 0).unwrap().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cancel_endless_loop() {
|
||||||
|
let barrier = Arc::new(Barrier::new(2));
|
||||||
|
let b = barrier.clone();
|
||||||
|
|
||||||
|
// Create a runloop that never exits.
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
b.wait();
|
||||||
|
while alive() { /* loop */ }
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
barrier.wait();
|
||||||
|
assert!(thread.alive());
|
||||||
|
thread.cancel();
|
||||||
|
assert!(!thread.alive());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timeout() {
|
||||||
|
// Create a runloop that never exits, but times out after a second.
|
||||||
|
let thread = RunLoop::new(|alive| while alive() {}, 1).unwrap();
|
||||||
|
|
||||||
|
while thread.alive() { /* wait */ }
|
||||||
|
assert!(!thread.alive());
|
||||||
|
thread.cancel(); // noop
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef __U2FHID_CAPI
|
||||||
|
#define __U2FHID_CAPI
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "nsString.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
|
||||||
|
const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
|
||||||
|
const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
|
||||||
|
|
||||||
|
// NOTE: Preconditions
|
||||||
|
// * All rust_u2f_mgr* pointers must refer to pointers which are returned
|
||||||
|
// by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
|
||||||
|
// * All rust_u2f_khs* pointers must refer to pointers which are returned
|
||||||
|
// by rust_u2f_khs_new, and must be freed with rust_u2f_khs_free.
|
||||||
|
// * All rust_u2f_res* pointers must refer to pointers passed to the
|
||||||
|
// register() and sign() callbacks. They can be null on failure.
|
||||||
|
|
||||||
|
// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
|
||||||
|
struct rust_u2f_manager;
|
||||||
|
|
||||||
|
// The `rust_u2f_key_handles` opaque type is equivalent to the rust type `U2FKeyHandles`
|
||||||
|
struct rust_u2f_key_handles;
|
||||||
|
|
||||||
|
// The `rust_u2f_res` opaque type is equivalent to the rust type `U2FResult`
|
||||||
|
struct rust_u2f_result;
|
||||||
|
|
||||||
|
// The callback passed to register() and sign().
|
||||||
|
typedef void (*rust_u2f_callback)(uint64_t, rust_u2f_result*);
|
||||||
|
|
||||||
|
|
||||||
|
/// U2FManager functions.
|
||||||
|
|
||||||
|
rust_u2f_manager* rust_u2f_mgr_new();
|
||||||
|
/* unsafe */ void rust_u2f_mgr_free(rust_u2f_manager* mgr);
|
||||||
|
|
||||||
|
uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr,
|
||||||
|
uint64_t timeout,
|
||||||
|
rust_u2f_callback,
|
||||||
|
const uint8_t* challenge_ptr,
|
||||||
|
size_t challenge_len,
|
||||||
|
const uint8_t* application_ptr,
|
||||||
|
size_t application_len);
|
||||||
|
|
||||||
|
uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
|
||||||
|
uint64_t timeout,
|
||||||
|
rust_u2f_callback,
|
||||||
|
const uint8_t* challenge_ptr,
|
||||||
|
size_t challenge_len,
|
||||||
|
const uint8_t* application_ptr,
|
||||||
|
size_t application_len,
|
||||||
|
const rust_u2f_key_handles* khs);
|
||||||
|
|
||||||
|
uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
|
||||||
|
|
||||||
|
|
||||||
|
/// U2FKeyHandles functions.
|
||||||
|
|
||||||
|
rust_u2f_key_handles* rust_u2f_khs_new();
|
||||||
|
void rust_u2f_khs_add(rust_u2f_key_handles* khs,
|
||||||
|
const uint8_t* key_handle,
|
||||||
|
size_t key_handle_len);
|
||||||
|
/* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);
|
||||||
|
|
||||||
|
|
||||||
|
/// U2FResult functions.
|
||||||
|
|
||||||
|
// Call this before `[..]_copy()` to allocate enough space.
|
||||||
|
bool rust_u2f_resbuf_length(const rust_u2f_result *res, uint8_t bid, size_t* len);
|
||||||
|
bool rust_u2f_resbuf_copy(const rust_u2f_result *res, uint8_t bid, uint8_t* dst);
|
||||||
|
/* unsafe */ void rust_u2f_res_free(rust_u2f_result* res);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __U2FHID_CAPI
|
|
@ -0,0 +1,441 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use consts::*;
|
||||||
|
use u2ftypes::*;
|
||||||
|
use util::io_err;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Device Commands
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub fn u2f_init_device<T>(dev: &mut T) -> bool
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
// Do a few U2F device checks.
|
||||||
|
let mut nonce = [0u8; 8];
|
||||||
|
thread_rng().fill_bytes(&mut nonce);
|
||||||
|
if init_device(dev, &nonce).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut random = [0u8; 8];
|
||||||
|
thread_rng().fill_bytes(&mut random);
|
||||||
|
if ping_device(dev, &random).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_v2_device(dev).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u2f_register<T>(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid parameter sizes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE);
|
||||||
|
register_data.extend(challenge);
|
||||||
|
register_data.extend(application);
|
||||||
|
|
||||||
|
let flags = U2F_REQUEST_USER_PRESENCE;
|
||||||
|
let (resp, status) = send_apdu(dev, U2F_REGISTER, flags, ®ister_data)?;
|
||||||
|
status_word_to_result(status, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u2f_sign<T>(
|
||||||
|
dev: &mut T,
|
||||||
|
challenge: &[u8],
|
||||||
|
application: &[u8],
|
||||||
|
key_handle: &[u8],
|
||||||
|
) -> io::Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid parameter sizes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if key_handle.len() > 256 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Key handle too large",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len());
|
||||||
|
sign_data.extend(challenge);
|
||||||
|
sign_data.extend(application);
|
||||||
|
sign_data.push(key_handle.len() as u8);
|
||||||
|
sign_data.extend(key_handle);
|
||||||
|
|
||||||
|
let flags = U2F_REQUEST_USER_PRESENCE;
|
||||||
|
let (resp, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?;
|
||||||
|
status_word_to_result(status, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u2f_is_keyhandle_valid<T>(
|
||||||
|
dev: &mut T,
|
||||||
|
challenge: &[u8],
|
||||||
|
application: &[u8],
|
||||||
|
key_handle: &[u8],
|
||||||
|
) -> io::Result<bool>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid parameter sizes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if key_handle.len() > 256 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Key handle too large",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len());
|
||||||
|
sign_data.extend(challenge);
|
||||||
|
sign_data.extend(application);
|
||||||
|
sign_data.push(key_handle.len() as u8);
|
||||||
|
sign_data.extend(key_handle);
|
||||||
|
|
||||||
|
let flags = U2F_CHECK_IS_REGISTERED;
|
||||||
|
let (_, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?;
|
||||||
|
Ok(status == SW_CONDITIONS_NOT_SATISFIED)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal Device Commands
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
fn init_device<T>(dev: &mut T, nonce: &[u8]) -> io::Result<()>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
|
||||||
|
let raw = sendrecv(dev, U2FHID_INIT, nonce)?;
|
||||||
|
dev.set_cid(U2FHIDInitResp::read(&raw, nonce)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_device<T>(dev: &mut T, random: &[u8]) -> io::Result<()>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
assert_eq!(random.len(), 8);
|
||||||
|
if sendrecv(dev, U2FHID_PING, random)? != random {
|
||||||
|
return Err(io_err("Ping was corrupted!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_v2_device<T>(dev: &mut T) -> io::Result<bool>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
let (data, status) = send_apdu(dev, U2F_VERSION, 0x00, &[])?;
|
||||||
|
let actual = CString::new(data)?;
|
||||||
|
let expected = CString::new("U2F_V2")?;
|
||||||
|
status_word_to_result(status, actual == expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Error Handling
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
fn status_word_to_result<T>(status: [u8; 2], val: T) -> io::Result<T> {
|
||||||
|
use self::io::ErrorKind::{InvalidData, InvalidInput};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
SW_NO_ERROR => Ok(val),
|
||||||
|
SW_WRONG_DATA => Err(io::Error::new(InvalidData, "wrong data")),
|
||||||
|
SW_WRONG_LENGTH => Err(io::Error::new(InvalidInput, "wrong length")),
|
||||||
|
SW_CONDITIONS_NOT_SATISFIED => Err(io_err("conditions not satisfied")),
|
||||||
|
_ => Err(io_err(&format!("failed with status {:?}", status))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Device Communication Functions
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub fn sendrecv<T>(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
// Send initialization packet.
|
||||||
|
let mut count = U2FHIDInit::write(dev, cmd, send)?;
|
||||||
|
|
||||||
|
// Send continuation packets.
|
||||||
|
let mut sequence = 0u8;
|
||||||
|
while count < send.len() {
|
||||||
|
count += U2FHIDCont::write(dev, sequence, &send[count..])?;
|
||||||
|
sequence += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we read. This happens in 2 chunks: The initial packet, which has the
|
||||||
|
// size we expect overall, then continuation packets, which will fill in
|
||||||
|
// data until we have everything.
|
||||||
|
let mut data = U2FHIDInit::read(dev)?;
|
||||||
|
|
||||||
|
let mut sequence = 0u8;
|
||||||
|
while data.len() < data.capacity() {
|
||||||
|
let max = data.capacity() - data.len();
|
||||||
|
data.extend_from_slice(&U2FHIDCont::read(dev, sequence, max)?);
|
||||||
|
sequence += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_apdu<T>(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])>
|
||||||
|
where
|
||||||
|
T: U2FDevice + Read + Write,
|
||||||
|
{
|
||||||
|
let apdu = U2FAPDUHeader::to_bytes(cmd, p1, send)?;
|
||||||
|
let mut data = sendrecv(dev, U2FHID_MSG, &apdu)?;
|
||||||
|
|
||||||
|
if data.len() < 2 {
|
||||||
|
return Err(io_err("unexpected response"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let split_at = data.len() - 2;
|
||||||
|
let status = data.split_off(split_at);
|
||||||
|
Ok((data, [status[0], status[1]]))
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Tests
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
use super::{U2FDevice, init_device, ping_device, sendrecv, send_apdu};
|
||||||
|
use consts::{CID_BROADCAST, U2FHID_INIT, U2FHID_PING, U2FHID_MSG, SW_NO_ERROR};
|
||||||
|
|
||||||
|
mod platform {
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use consts::{CID_BROADCAST, HID_RPT_SIZE};
|
||||||
|
use u2ftypes::U2FDevice;
|
||||||
|
|
||||||
|
pub struct TestDevice {
|
||||||
|
cid: [u8; 4],
|
||||||
|
reads: Vec<[u8; HID_RPT_SIZE]>,
|
||||||
|
writes: Vec<[u8; HID_RPT_SIZE + 1]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestDevice {
|
||||||
|
pub fn new() -> TestDevice {
|
||||||
|
TestDevice {
|
||||||
|
cid: CID_BROADCAST,
|
||||||
|
reads: vec![],
|
||||||
|
writes: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_write(&mut self, packet: &[u8], fill_value: u8) {
|
||||||
|
// Add one to deal with record index check
|
||||||
|
let mut write = [fill_value; HID_RPT_SIZE + 1];
|
||||||
|
// Make sure we start with a 0, for HID record index
|
||||||
|
write[0] = 0;
|
||||||
|
// Clone packet data in at 1, since front is padded with HID record index
|
||||||
|
write[1..packet.len() + 1].clone_from_slice(packet);
|
||||||
|
self.writes.push(write);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_read(&mut self, packet: &[u8], fill_value: u8) {
|
||||||
|
let mut read = [fill_value; HID_RPT_SIZE];
|
||||||
|
read[..packet.len()].clone_from_slice(packet);
|
||||||
|
self.reads.push(read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for TestDevice {
|
||||||
|
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||||
|
// Pop a vector from the expected writes, check for quality
|
||||||
|
// against bytes array.
|
||||||
|
assert!(self.writes.len() > 0, "Ran out of expected write values!");
|
||||||
|
let check = self.writes.remove(0);
|
||||||
|
assert_eq!(check.len(), bytes.len());
|
||||||
|
assert_eq!(&check[..], bytes);
|
||||||
|
Ok(bytes.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// nop
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for TestDevice {
|
||||||
|
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
|
||||||
|
assert!(self.reads.len() > 0, "Ran out of read values!");
|
||||||
|
let check = self.reads.remove(0);
|
||||||
|
assert_eq!(check.len(), bytes.len());
|
||||||
|
bytes.clone_from_slice(&check[..]);
|
||||||
|
Ok(check.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestDevice {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
assert_eq!(self.reads.len(), 0);
|
||||||
|
assert_eq!(self.writes.len(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U2FDevice for TestDevice {
|
||||||
|
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
|
||||||
|
&self.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||||
|
self.cid = cid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_device() {
|
||||||
|
let mut device = platform::TestDevice::new();
|
||||||
|
let nonce = vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
|
||||||
|
|
||||||
|
// channel id
|
||||||
|
let mut cid = [0u8; 4];
|
||||||
|
thread_rng().fill_bytes(&mut cid);
|
||||||
|
|
||||||
|
// init packet
|
||||||
|
let mut msg = CID_BROADCAST.to_vec();
|
||||||
|
msg.extend(vec![U2FHID_INIT, 0x00, 0x08]); // cmd + bcnt
|
||||||
|
msg.extend_from_slice(&nonce);
|
||||||
|
device.add_write(&msg, 0);
|
||||||
|
|
||||||
|
// init_resp packet
|
||||||
|
let mut msg = CID_BROADCAST.to_vec();
|
||||||
|
msg.extend(vec![U2FHID_INIT, 0x00, 0x11]); // cmd + bcnt
|
||||||
|
msg.extend_from_slice(&nonce);
|
||||||
|
msg.extend_from_slice(&cid); // new channel id
|
||||||
|
msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags
|
||||||
|
device.add_read(&msg, 0);
|
||||||
|
|
||||||
|
init_device(&mut device, &nonce).unwrap();
|
||||||
|
assert_eq!(device.get_cid(), &cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sendrecv_multiple() {
|
||||||
|
let mut device = platform::TestDevice::new();
|
||||||
|
let cid = [0x01, 0x02, 0x03, 0x04];
|
||||||
|
device.set_cid(cid.clone());
|
||||||
|
|
||||||
|
// init packet
|
||||||
|
let mut msg = cid.to_vec();
|
||||||
|
msg.extend(vec![U2FHID_PING, 0x00, 0xe4]); // cmd + length = 228
|
||||||
|
// write msg, append [1u8; 57], 171 bytes remaining
|
||||||
|
device.add_write(&msg, 1);
|
||||||
|
device.add_read(&msg, 1);
|
||||||
|
|
||||||
|
// cont packet
|
||||||
|
let mut msg = cid.to_vec();
|
||||||
|
msg.push(0x00); // seq = 0
|
||||||
|
// write msg, append [1u8; 59], 112 bytes remaining
|
||||||
|
device.add_write(&msg, 1);
|
||||||
|
device.add_read(&msg, 1);
|
||||||
|
|
||||||
|
// cont packet
|
||||||
|
let mut msg = cid.to_vec();
|
||||||
|
msg.push(0x01); // seq = 1
|
||||||
|
// write msg, append [1u8; 59], 53 bytes remaining
|
||||||
|
device.add_write(&msg, 1);
|
||||||
|
device.add_read(&msg, 1);
|
||||||
|
|
||||||
|
// cont packet
|
||||||
|
let mut msg = cid.to_vec();
|
||||||
|
msg.push(0x02); // seq = 2
|
||||||
|
msg.extend_from_slice(&[1u8; 53]);
|
||||||
|
// write msg, append remaining 53 bytes.
|
||||||
|
device.add_write(&msg, 0);
|
||||||
|
device.add_read(&msg, 0);
|
||||||
|
|
||||||
|
let data = [1u8; 228];
|
||||||
|
let d = sendrecv(&mut device, U2FHID_PING, &data).unwrap();
|
||||||
|
assert_eq!(d.len(), 228);
|
||||||
|
assert_eq!(d, &data[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sendapdu() {
|
||||||
|
let cid = [0x01, 0x02, 0x03, 0x04];
|
||||||
|
let data = [0x01, 0x02, 0x03, 0x04, 0x05];
|
||||||
|
let mut device = platform::TestDevice::new();
|
||||||
|
device.set_cid(cid.clone());
|
||||||
|
|
||||||
|
let mut msg = cid.to_vec();
|
||||||
|
// sendrecv header
|
||||||
|
msg.extend(vec![U2FHID_MSG, 0x00, 0x0e]); // len = 14
|
||||||
|
// apdu header
|
||||||
|
msg.extend(vec![0x00, U2FHID_PING, 0xaa, 0x00, 0x00, 0x00, 0x05]);
|
||||||
|
// apdu data
|
||||||
|
msg.extend_from_slice(&data);
|
||||||
|
device.add_write(&msg, 0);
|
||||||
|
|
||||||
|
// Send data back
|
||||||
|
let mut msg = cid.to_vec();
|
||||||
|
msg.extend(vec![U2FHID_MSG, 0x00, 0x07]);
|
||||||
|
msg.extend_from_slice(&data);
|
||||||
|
msg.extend_from_slice(&SW_NO_ERROR);
|
||||||
|
device.add_read(&msg, 0);
|
||||||
|
|
||||||
|
let (result, status) = send_apdu(&mut device, U2FHID_PING, 0xaa, &data).unwrap();
|
||||||
|
assert_eq!(result, &data);
|
||||||
|
assert_eq!(status, SW_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ping_device() {
|
||||||
|
let mut device = platform::TestDevice::new();
|
||||||
|
device.set_cid([0x01, 0x02, 0x03, 0x04]);
|
||||||
|
|
||||||
|
// ping nonce
|
||||||
|
let random = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
|
||||||
|
|
||||||
|
// APDU header
|
||||||
|
let mut msg = vec![0x01, 0x02, 0x03, 0x04, U2FHID_PING, 0x00, 0x08];
|
||||||
|
msg.extend_from_slice(&random);
|
||||||
|
device.add_write(&msg, 0);
|
||||||
|
|
||||||
|
// Only expect data from APDU back
|
||||||
|
let mut msg = vec![0x01, 0x02, 0x03, 0x04, U2FHID_MSG, 0x00, 0x08];
|
||||||
|
msg.extend_from_slice(&random);
|
||||||
|
device.add_read(&msg, 0);
|
||||||
|
|
||||||
|
ping_device(&mut device, &random).unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::{cmp, io};
|
||||||
|
|
||||||
|
use consts::*;
|
||||||
|
use util::io_err;
|
||||||
|
|
||||||
|
use log;
|
||||||
|
|
||||||
|
fn trace_hex(data: &[u8]) {
|
||||||
|
if log_enabled!(log::LogLevel::Trace) {
|
||||||
|
let parts: Vec<String> = data.iter().map(|byte| format!("{:02x}", byte)).collect();
|
||||||
|
trace!("USB send: {}", parts.join(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait for representing U2F HID Devices. Requires getters/setters for the
|
||||||
|
// channel ID, created during device initialization.
|
||||||
|
pub trait U2FDevice {
|
||||||
|
fn get_cid(&self) -> &[u8; 4];
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init structure for U2F Communications. Tells the receiver what channel
|
||||||
|
// communication is happening on, what command is running, and how much data to
|
||||||
|
// expect to receive over all.
|
||||||
|
//
|
||||||
|
// Spec at https://fidoalliance.org/specs/fido-u2f-v1.
|
||||||
|
// 0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.html#message--and-packet-structure
|
||||||
|
pub struct U2FHIDInit {}
|
||||||
|
|
||||||
|
impl U2FHIDInit {
|
||||||
|
pub fn read<T>(dev: &mut T) -> io::Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
T: U2FDevice + io::Read,
|
||||||
|
{
|
||||||
|
let mut frame = [0u8; HID_RPT_SIZE];
|
||||||
|
let count = dev.read(&mut frame)?;
|
||||||
|
|
||||||
|
if count != HID_RPT_SIZE {
|
||||||
|
return Err(io_err("invalid init packet"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if dev.get_cid() != &frame[..4] {
|
||||||
|
return Err(io_err("invalid channel id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let cap = (frame[5] as usize) << 8 | (frame[6] as usize);
|
||||||
|
let mut data = Vec::with_capacity(cap);
|
||||||
|
|
||||||
|
let len = cmp::min(cap, INIT_DATA_SIZE);
|
||||||
|
data.extend_from_slice(&frame[7..7 + len]);
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<T>(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: U2FDevice + io::Write,
|
||||||
|
{
|
||||||
|
if data.len() > 0xffff {
|
||||||
|
return Err(io_err("payload length > 2^16"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut frame = [0; HID_RPT_SIZE + 1];
|
||||||
|
frame[1..5].copy_from_slice(dev.get_cid());
|
||||||
|
frame[5] = cmd;
|
||||||
|
frame[6] = (data.len() >> 8) as u8;
|
||||||
|
frame[7] = data.len() as u8;
|
||||||
|
|
||||||
|
let count = cmp::min(data.len(), INIT_DATA_SIZE);
|
||||||
|
frame[8..8 + count].copy_from_slice(&data[..count]);
|
||||||
|
trace_hex(&frame);
|
||||||
|
|
||||||
|
if dev.write(&frame)? != frame.len() {
|
||||||
|
return Err(io_err("device write failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuation structure for U2F Communications. After an Init structure is
|
||||||
|
// sent, continuation structures are used to transmit all extra data that
|
||||||
|
// wouldn't fit in the initial packet. The sequence number increases with every
|
||||||
|
// packet, until all data is received.
|
||||||
|
//
|
||||||
|
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
|
||||||
|
// html#message--and-packet-structure
|
||||||
|
pub struct U2FHIDCont {}
|
||||||
|
|
||||||
|
impl U2FHIDCont {
|
||||||
|
pub fn read<T>(dev: &mut T, seq: u8, max: usize) -> io::Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
T: U2FDevice + io::Read,
|
||||||
|
{
|
||||||
|
let mut frame = [0u8; HID_RPT_SIZE];
|
||||||
|
let count = dev.read(&mut frame)?;
|
||||||
|
|
||||||
|
if count != HID_RPT_SIZE {
|
||||||
|
return Err(io_err("invalid cont packet"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if dev.get_cid() != &frame[..4] {
|
||||||
|
return Err(io_err("invalid channel id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if seq != frame[4] {
|
||||||
|
return Err(io_err("invalid sequence number"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let max = cmp::min(max, CONT_DATA_SIZE);
|
||||||
|
Ok(frame[5..5 + max].to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<T>(dev: &mut T, seq: u8, data: &[u8]) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: U2FDevice + io::Write,
|
||||||
|
{
|
||||||
|
let mut frame = [0; HID_RPT_SIZE + 1];
|
||||||
|
frame[1..5].copy_from_slice(dev.get_cid());
|
||||||
|
frame[5] = seq;
|
||||||
|
|
||||||
|
let count = cmp::min(data.len(), CONT_DATA_SIZE);
|
||||||
|
frame[6..6 + count].copy_from_slice(&data[..count]);
|
||||||
|
trace_hex(&frame);
|
||||||
|
|
||||||
|
if dev.write(&frame)? != frame.len() {
|
||||||
|
return Err(io_err("device write failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Reply sent after initialization command. Contains information about U2F USB
|
||||||
|
// Key versioning, as well as the communication channel to be used for all
|
||||||
|
// further requests.
|
||||||
|
//
|
||||||
|
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
|
||||||
|
// html#u2fhid_init
|
||||||
|
pub struct U2FHIDInitResp {}
|
||||||
|
|
||||||
|
impl U2FHIDInitResp {
|
||||||
|
pub fn read(data: &[u8], nonce: &[u8]) -> io::Result<[u8; 4]> {
|
||||||
|
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
|
||||||
|
|
||||||
|
if data.len() != INIT_NONCE_SIZE + 9 {
|
||||||
|
return Err(io_err("invalid init response"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if nonce != &data[..INIT_NONCE_SIZE] {
|
||||||
|
return Err(io_err("invalid nonce"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cid = [0u8; 4];
|
||||||
|
cid.copy_from_slice(&data[INIT_NONCE_SIZE..INIT_NONCE_SIZE + 4]);
|
||||||
|
Ok(cid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit
|
||||||
|
// https://fidoalliance.org/specs/fido-u2f-v1.
|
||||||
|
// 0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#u2f-message-framing
|
||||||
|
pub struct U2FAPDUHeader {}
|
||||||
|
|
||||||
|
impl U2FAPDUHeader {
|
||||||
|
pub fn to_bytes(ins: u8, p1: u8, data: &[u8]) -> io::Result<Vec<u8>> {
|
||||||
|
if data.len() > 0xffff {
|
||||||
|
return Err(io_err("payload length > 2^16"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size of header + data + 2 zero bytes for maximum return size.
|
||||||
|
let mut bytes = vec![0u8; U2FAPDUHEADER_SIZE + data.len() + 2];
|
||||||
|
bytes[1] = ins;
|
||||||
|
bytes[2] = p1;
|
||||||
|
// p2 is always 0, at least, for our requirements.
|
||||||
|
// lc[0] should always be 0.
|
||||||
|
bytes[5] = (data.len() >> 8) as u8;
|
||||||
|
bytes[6] = data.len() as u8;
|
||||||
|
bytes[7..7 + data.len()].copy_from_slice(data);
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use boxfnonce::SendBoxFnOnce;
|
||||||
|
|
||||||
|
macro_rules! try_or {
|
||||||
|
($val:expr, $or:expr) => {
|
||||||
|
match $val {
|
||||||
|
Ok(v) => { v }
|
||||||
|
Err(e) => { return $or(e); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Signed {
|
||||||
|
fn is_negative(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signed for i32 {
|
||||||
|
fn is_negative(&self) -> bool {
|
||||||
|
*self < (0 as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signed for usize {
|
||||||
|
fn is_negative(&self) -> bool {
|
||||||
|
(*self as isize) < (0 as isize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux"))]
|
||||||
|
pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
|
||||||
|
if rv.is_negative() {
|
||||||
|
let errno = unsafe { *libc::__errno_location() };
|
||||||
|
Err(io::Error::from_raw_os_error(errno))
|
||||||
|
} else {
|
||||||
|
Ok(rv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn io_err(msg: &str) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::Other, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_io_err<T: Error>(err: T) -> io::Error {
|
||||||
|
io_err(err.description())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OnceCallback<T> {
|
||||||
|
callback: Arc<Mutex<Option<SendBoxFnOnce<(io::Result<T>,)>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OnceCallback<T> {
|
||||||
|
pub fn new<F>(cb: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce(io::Result<T>),
|
||||||
|
F: Send + 'static,
|
||||||
|
{
|
||||||
|
let cb = Some(SendBoxFnOnce::from(cb));
|
||||||
|
Self { callback: Arc::new(Mutex::new(cb)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, rv: io::Result<T>) {
|
||||||
|
if let Ok(mut cb) = self.callback.lock() {
|
||||||
|
if let Some(cb) = cb.take() {
|
||||||
|
cb.call(rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for OnceCallback<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { callback: self.callback.clone() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::os::windows::io::AsRawHandle;
|
||||||
|
|
||||||
|
use consts::{CID_BROADCAST, HID_RPT_SIZE, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
|
||||||
|
use super::winapi::DeviceCapabilities;
|
||||||
|
|
||||||
|
use u2ftypes::U2FDevice;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Device {
|
||||||
|
path: String,
|
||||||
|
file: File,
|
||||||
|
cid: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn new(path: String) -> io::Result<Self> {
|
||||||
|
let file = OpenOptions::new().read(true).write(true).open(&path)?;
|
||||||
|
Ok(Self {
|
||||||
|
path: path,
|
||||||
|
file: file,
|
||||||
|
cid: CID_BROADCAST,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_u2f(&self) -> bool {
|
||||||
|
match DeviceCapabilities::new(self.file.as_raw_handle()) {
|
||||||
|
Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Device {
|
||||||
|
fn eq(&self, other: &Device) -> bool {
|
||||||
|
self.path == other.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Device {
|
||||||
|
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
|
||||||
|
// Windows always includes the report ID.
|
||||||
|
let mut input = [0u8; HID_RPT_SIZE + 1];
|
||||||
|
let _ = self.file.read(&mut input)?;
|
||||||
|
bytes.clone_from_slice(&input[1..]);
|
||||||
|
Ok(bytes.len() as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Device {
|
||||||
|
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||||
|
self.file.write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.file.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U2FDevice for Device {
|
||||||
|
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
|
||||||
|
&self.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||||
|
self.cid = cid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::collections::hash_map::ValuesMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use platform::device::Device;
|
||||||
|
use platform::monitor::Event;
|
||||||
|
use u2fprotocol::u2f_init_device;
|
||||||
|
|
||||||
|
pub struct DeviceMap {
|
||||||
|
map: HashMap<String, Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceMap {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { map: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values_mut(&mut self) -> ValuesMut<String, Device> {
|
||||||
|
self.map.values_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_event(&mut self, event: Event) {
|
||||||
|
match event {
|
||||||
|
Event::Add(path) => self.add(path),
|
||||||
|
Event::Remove(path) => self.remove(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, path: String) {
|
||||||
|
if self.map.contains_key(&path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and try to open the device.
|
||||||
|
if let Ok(mut dev) = Device::new(path.clone()) {
|
||||||
|
if dev.is_u2f() && u2f_init_device(&mut dev) {
|
||||||
|
self.map.insert(path, dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, path: String) {
|
||||||
|
// Ignore errors.
|
||||||
|
let _ = self.map.remove(&path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
mod device;
|
||||||
|
mod devicemap;
|
||||||
|
mod monitor;
|
||||||
|
mod winapi;
|
||||||
|
|
||||||
|
use consts::PARAMETER_SIZE;
|
||||||
|
use runloop::RunLoop;
|
||||||
|
use util::{io_err, OnceCallback};
|
||||||
|
use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid};
|
||||||
|
|
||||||
|
use self::devicemap::DeviceMap;
|
||||||
|
use self::monitor::Monitor;
|
||||||
|
|
||||||
|
pub struct PlatformManager {
|
||||||
|
// Handle to the thread loop.
|
||||||
|
thread: Option<RunLoop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { thread: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
callback: OnceCallback<Vec<u8>>,
|
||||||
|
) {
|
||||||
|
// Abort any prior register/sign calls.
|
||||||
|
self.cancel();
|
||||||
|
|
||||||
|
let cbc = callback.clone();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut devices = DeviceMap::new();
|
||||||
|
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
|
||||||
|
|
||||||
|
while alive() && monitor.alive() {
|
||||||
|
// Add/remove devices.
|
||||||
|
for event in monitor.events() {
|
||||||
|
devices.process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to register each device.
|
||||||
|
for device in devices.values_mut() {
|
||||||
|
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
|
||||||
|
callback.call(Ok(bytes));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a little before trying again.
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(Err(io_err("aborted or timed out")));
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.thread = Some(try_or!(thread, |_| {
|
||||||
|
cbc.call(Err(io_err("couldn't create runloop")));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(
|
||||||
|
&mut self,
|
||||||
|
timeout: u64,
|
||||||
|
challenge: Vec<u8>,
|
||||||
|
application: Vec<u8>,
|
||||||
|
key_handles: Vec<Vec<u8>>,
|
||||||
|
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||||
|
) {
|
||||||
|
// Abort any prior register/sign calls.
|
||||||
|
self.cancel();
|
||||||
|
|
||||||
|
let cbc = callback.clone();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| {
|
||||||
|
let mut devices = DeviceMap::new();
|
||||||
|
let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
|
||||||
|
|
||||||
|
while alive() && monitor.alive() {
|
||||||
|
// Add/remove devices.
|
||||||
|
for event in monitor.events() {
|
||||||
|
devices.process_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try signing with each device.
|
||||||
|
for key_handle in &key_handles {
|
||||||
|
for device in devices.values_mut() {
|
||||||
|
// Check if they key handle belongs to the current device.
|
||||||
|
let is_valid = match u2f_is_keyhandle_valid(
|
||||||
|
device,
|
||||||
|
&challenge,
|
||||||
|
&application,
|
||||||
|
&key_handle,
|
||||||
|
) {
|
||||||
|
Ok(valid) => valid,
|
||||||
|
Err(_) => continue, // Skip this device for now.
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
// If yes, try to sign.
|
||||||
|
if let Ok(bytes) = u2f_sign(
|
||||||
|
device,
|
||||||
|
&challenge,
|
||||||
|
&application,
|
||||||
|
&key_handle,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
callback.call(Ok((key_handle.clone(), bytes)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no, keep registering and blinking with bogus data
|
||||||
|
let blank = vec![0u8; PARAMETER_SIZE];
|
||||||
|
if let Ok(_) = u2f_register(device, &blank, &blank) {
|
||||||
|
callback.call(Err(io_err("invalid key")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a little before trying again.
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(Err(io_err("aborted or timed out")));
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.thread = Some(try_or!(thread, |_| {
|
||||||
|
cbc.call(Err(io_err("couldn't create runloop")));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This might block.
|
||||||
|
pub fn cancel(&mut self) {
|
||||||
|
if let Some(thread) = self.thread.take() {
|
||||||
|
thread.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use std::sync::mpsc::{channel, Receiver, TryIter};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use runloop::RunLoop;
|
||||||
|
use super::winapi::DeviceInfoSet;
|
||||||
|
|
||||||
|
pub fn io_err(msg: &str) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::Other, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_io_err<T: Error>(err: T) -> io::Error {
|
||||||
|
io_err(err.description())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Add(String),
|
||||||
|
Remove(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Monitor {
|
||||||
|
// Receive events from the thread.
|
||||||
|
rx: Receiver<Event>,
|
||||||
|
// Handle to the thread loop.
|
||||||
|
thread: RunLoop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monitor {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let thread = RunLoop::new(
|
||||||
|
move |alive| -> io::Result<()> {
|
||||||
|
let mut stored = HashSet::new();
|
||||||
|
|
||||||
|
while alive() {
|
||||||
|
let device_info_set = DeviceInfoSet::new()?;
|
||||||
|
let devices = HashSet::from_iter(device_info_set.devices());
|
||||||
|
|
||||||
|
// Remove devices that are gone.
|
||||||
|
for path in stored.difference(&devices) {
|
||||||
|
tx.send(Event::Remove(path.clone())).map_err(to_io_err)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add devices that were plugged in.
|
||||||
|
for path in devices.difference(&stored) {
|
||||||
|
tx.send(Event::Add(path.clone())).map_err(to_io_err)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember the new set.
|
||||||
|
stored = devices;
|
||||||
|
|
||||||
|
// Wait a little before looking for devices again.
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
0, /* no timeout */
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
rx: rx,
|
||||||
|
thread: thread,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events<'a>(&'a self) -> TryIter<'a, Event> {
|
||||||
|
self.rx.try_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
self.thread.alive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Monitor {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.thread.cancel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::os::windows::ffi::OsStringExt;
|
||||||
|
|
||||||
|
use util::io_err;
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
extern crate winapi;
|
||||||
|
use self::winapi::*;
|
||||||
|
|
||||||
|
#[link(name = "setupapi")]
|
||||||
|
extern "stdcall" {
|
||||||
|
fn SetupDiGetClassDevsW(
|
||||||
|
ClassGuid: *const GUID,
|
||||||
|
Enumerator: PCSTR,
|
||||||
|
hwndParent: HWND,
|
||||||
|
flags: DWORD,
|
||||||
|
) -> HDEVINFO;
|
||||||
|
|
||||||
|
fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: HDEVINFO) -> BOOL;
|
||||||
|
|
||||||
|
fn SetupDiEnumDeviceInterfaces(
|
||||||
|
DeviceInfoSet: HDEVINFO,
|
||||||
|
DeviceInfoData: PSP_DEVINFO_DATA,
|
||||||
|
InterfaceClassGuid: *const GUID,
|
||||||
|
MemberIndex: DWORD,
|
||||||
|
DeviceInterfaceData: PSP_DEVICE_INTERFACE_DATA,
|
||||||
|
) -> BOOL;
|
||||||
|
|
||||||
|
fn SetupDiGetDeviceInterfaceDetailW(
|
||||||
|
DeviceInfoSet: HDEVINFO,
|
||||||
|
DeviceInterfaceData: PSP_DEVICE_INTERFACE_DATA,
|
||||||
|
DeviceInterfaceDetailData: PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
|
||||||
|
DeviceInterfaceDetailDataSize: DWORD,
|
||||||
|
RequiredSize: PDWORD,
|
||||||
|
DeviceInfoData: PSP_DEVINFO_DATA,
|
||||||
|
) -> BOOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link(name = "hid")]
|
||||||
|
extern "stdcall" {
|
||||||
|
fn HidD_GetPreparsedData(
|
||||||
|
HidDeviceObject: HANDLE,
|
||||||
|
PreparsedData: *mut PHIDP_PREPARSED_DATA,
|
||||||
|
) -> BOOLEAN;
|
||||||
|
|
||||||
|
fn HidD_FreePreparsedData(PreparsedData: PHIDP_PREPARSED_DATA) -> BOOLEAN;
|
||||||
|
|
||||||
|
fn HidP_GetCaps(PreparsedData: PHIDP_PREPARSED_DATA, Capabilities: PHIDP_CAPS) -> NTSTATUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! offset_of {
|
||||||
|
($ty:ty, $field:ident) => {
|
||||||
|
unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_wide_ptr(ptr: *const u16, len: usize) -> String {
|
||||||
|
assert!(!ptr.is_null() && len % 2 == 0);
|
||||||
|
let slice = unsafe { slice::from_raw_parts(ptr, len / 2) };
|
||||||
|
OsString::from_wide(slice).to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceInfoSet {
|
||||||
|
set: HDEVINFO,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceInfoSet {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let flags = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE;
|
||||||
|
let set = unsafe {
|
||||||
|
SetupDiGetClassDevsW(
|
||||||
|
&GUID_DEVINTERFACE_HID,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
flags,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if set == INVALID_HANDLE_VALUE {
|
||||||
|
return Err(io_err("SetupDiGetClassDevsW failed!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { set })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> HDEVINFO {
|
||||||
|
self.set
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn devices(&self) -> DeviceInfoSetIter {
|
||||||
|
DeviceInfoSetIter::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DeviceInfoSet {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = unsafe { SetupDiDestroyDeviceInfoList(self.set) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceInfoSetIter<'a> {
|
||||||
|
set: &'a DeviceInfoSet,
|
||||||
|
index: DWORD,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DeviceInfoSetIter<'a> {
|
||||||
|
fn new(set: &'a DeviceInfoSet) -> Self {
|
||||||
|
Self { set, index: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DeviceInfoSetIter<'a> {
|
||||||
|
type Item = String;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let mut device_interface_data = unsafe { mem::uninitialized::<SP_DEVICE_INTERFACE_DATA>() };
|
||||||
|
device_interface_data.cbSize = mem::size_of::<SP_DEVICE_INTERFACE_DATA>() as UINT;
|
||||||
|
|
||||||
|
let rv = unsafe {
|
||||||
|
SetupDiEnumDeviceInterfaces(
|
||||||
|
self.set.get(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
&GUID_DEVINTERFACE_HID,
|
||||||
|
self.index,
|
||||||
|
&mut device_interface_data,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if rv == 0 {
|
||||||
|
return None; // We're past the last device index.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the size required to hold a detail struct.
|
||||||
|
let mut required_size = 0;
|
||||||
|
unsafe {
|
||||||
|
SetupDiGetDeviceInterfaceDetailW(
|
||||||
|
self.set.get(),
|
||||||
|
&mut device_interface_data,
|
||||||
|
ptr::null_mut(),
|
||||||
|
required_size,
|
||||||
|
&mut required_size,
|
||||||
|
ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if required_size == 0 {
|
||||||
|
return None; // An error occurred.
|
||||||
|
}
|
||||||
|
|
||||||
|
let detail = DeviceInterfaceDetailData::new(required_size as usize);
|
||||||
|
if detail.is_none() {
|
||||||
|
return None; // malloc() failed.
|
||||||
|
}
|
||||||
|
|
||||||
|
let detail = detail.unwrap();
|
||||||
|
let rv = unsafe {
|
||||||
|
SetupDiGetDeviceInterfaceDetailW(
|
||||||
|
self.set.get(),
|
||||||
|
&mut device_interface_data,
|
||||||
|
detail.get(),
|
||||||
|
required_size,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if rv == 0 {
|
||||||
|
return None; // An error occurred.
|
||||||
|
}
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
|
Some(detail.path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceInterfaceDetailData {
|
||||||
|
data: PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
|
||||||
|
path_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceInterfaceDetailData {
|
||||||
|
fn new(size: usize) -> Option<Self> {
|
||||||
|
let mut cb_size = mem::size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>();
|
||||||
|
if cfg!(target_pointer_width = "32") {
|
||||||
|
cb_size = 4 + 2; // 4-byte uint + default TCHAR size. size_of is inaccurate.
|
||||||
|
}
|
||||||
|
|
||||||
|
if size < cb_size {
|
||||||
|
warn!("DeviceInterfaceDetailData is too small. {}", size);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = unsafe { libc::malloc(size) as PSP_DEVICE_INTERFACE_DETAIL_DATA_W };
|
||||||
|
if data.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set total size of the structure.
|
||||||
|
unsafe { (*data).cbSize = cb_size as UINT };
|
||||||
|
|
||||||
|
// Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`.
|
||||||
|
let offset = offset_of!(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
data,
|
||||||
|
path_len: size - offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self) -> PSP_DEVICE_INTERFACE_DETAIL_DATA_W {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> String {
|
||||||
|
unsafe { from_wide_ptr((*self.data).DevicePath.as_ptr(), self.path_len - 2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DeviceInterfaceDetailData {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { libc::free(self.data as *mut libc::c_void) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceCapabilities {
|
||||||
|
caps: HIDP_CAPS,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceCapabilities {
|
||||||
|
pub fn new(handle: HANDLE) -> io::Result<Self> {
|
||||||
|
let mut preparsed_data = ptr::null_mut();
|
||||||
|
let rv = unsafe { HidD_GetPreparsedData(handle, &mut preparsed_data) };
|
||||||
|
if rv == 0 || preparsed_data.is_null() {
|
||||||
|
return Err(io_err("HidD_GetPreparsedData failed!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut caps: HIDP_CAPS = unsafe { mem::uninitialized() };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let rv = HidP_GetCaps(preparsed_data, &mut caps);
|
||||||
|
HidD_FreePreparsedData(preparsed_data);
|
||||||
|
|
||||||
|
if rv != HIDP_STATUS_SUCCESS {
|
||||||
|
return Err(io_err("HidP_GetCaps failed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { caps })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usage(&self) -> USAGE {
|
||||||
|
self.caps.Usage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usage_page(&self) -> USAGE {
|
||||||
|
self.caps.UsagePage
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче