From dd24dc77e0b0b2e3470d9ee5d65abc281a0009e3 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 9 Aug 2017 21:16:49 +0200 Subject: [PATCH] Bug 1388843 - Part 1: Copy u2f-hid-rs into dom/webauthn/ r=gerv,qdot --- dom/webauthn/u2f-hid-rs/.gitignore | 9 + dom/webauthn/u2f-hid-rs/.travis.yml | 30 ++ dom/webauthn/u2f-hid-rs/Cargo.toml | 25 + dom/webauthn/u2f-hid-rs/LICENSE | 374 +++++++++++++++ dom/webauthn/u2f-hid-rs/README.md | 50 ++ dom/webauthn/u2f-hid-rs/build.rs | 8 + dom/webauthn/u2f-hid-rs/examples/main.rs | 79 ++++ dom/webauthn/u2f-hid-rs/fuzz/.gitignore | 2 + dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml | 29 ++ .../1172fb09a3c99ed54c7d677faae5386d938d5834 | Bin 0 -> 64 bytes .../3e28ec02ceb803efa105dc82fcfc9cb4014d7ced | Bin 0 -> 64 bytes .../5232969ad85258a14c3fbdb7b3e112539755f3a4 | Bin 0 -> 140 bytes .../5814556fab57a61da7d4150c2b45c15686df25ec | Bin 0 -> 151 bytes .../60c1a897bef5145eb977461d13f789e1ddfa4a69 | Bin 0 -> 512 bytes .../7f960b178bcf3adf082da7c632b4e1366ba22274 | Bin 0 -> 512 bytes .../a22d2b1daa9483abc026fa2f75290aa51be59ca9 | Bin 0 -> 226 bytes .../a5142fc55a5d72c9978bd54c73c8e32743bd8cfe | Bin 0 -> 110 bytes .../ab4368e52f60dbe956d28100adf2a4d110b615ec | Bin 0 -> 105 bytes .../1172fb09a3c99ed54c7d677faae5386d938d5834 | Bin 0 -> 64 bytes .../62b74bc0ad2433f77a007b95eaf964ea719d21e2 | Bin 0 -> 512 bytes .../680d38f668ab4372aaf0d57910c30a5ce6a6a03d | Bin 0 -> 33 bytes .../f94c8e14f32ca38e773745c5622e2da2a18c3a47 | Bin 0 -> 128 bytes .../u2f-hid-rs/fuzz/fuzz_targets/u2f_read.rs | 65 +++ .../fuzz/fuzz_targets/u2f_read_write.rs | 67 +++ dom/webauthn/u2f-hid-rs/rustfmt.toml | 2 + dom/webauthn/u2f-hid-rs/src/android/mod.rs | 44 ++ dom/webauthn/u2f-hid-rs/src/capi.rs | 197 ++++++++ dom/webauthn/u2f-hid-rs/src/consts.rs | 78 ++++ dom/webauthn/u2f-hid-rs/src/lib.rs | 52 +++ dom/webauthn/u2f-hid-rs/src/linux/device.rs | 83 ++++ .../u2f-hid-rs/src/linux/devicemap.rs | 50 ++ dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs | 216 +++++++++ dom/webauthn/u2f-hid-rs/src/linux/mod.rs | 159 +++++++ dom/webauthn/u2f-hid-rs/src/linux/monitor.rs | 123 +++++ dom/webauthn/u2f-hid-rs/src/macos/device.rs | 192 ++++++++ .../u2f-hid-rs/src/macos/devicemap.rs | 51 ++ dom/webauthn/u2f-hid-rs/src/macos/iohid.rs | 127 +++++ dom/webauthn/u2f-hid-rs/src/macos/iokit.rs | 93 ++++ dom/webauthn/u2f-hid-rs/src/macos/mod.rs | 183 ++++++++ dom/webauthn/u2f-hid-rs/src/macos/monitor.rs | 119 +++++ dom/webauthn/u2f-hid-rs/src/manager.rs | 173 +++++++ dom/webauthn/u2f-hid-rs/src/runloop.rs | 146 ++++++ dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h | 80 ++++ dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs | 441 ++++++++++++++++++ dom/webauthn/u2f-hid-rs/src/u2ftypes.rs | 188 ++++++++ dom/webauthn/u2f-hid-rs/src/util.rs | 83 ++++ dom/webauthn/u2f-hid-rs/src/windows/device.rs | 74 +++ .../u2f-hid-rs/src/windows/devicemap.rs | 49 ++ dom/webauthn/u2f-hid-rs/src/windows/mod.rs | 157 +++++++ .../u2f-hid-rs/src/windows/monitor.rs | 89 ++++ dom/webauthn/u2f-hid-rs/src/windows/winapi.rs | 263 +++++++++++ 51 files changed, 4250 insertions(+) create mode 100644 dom/webauthn/u2f-hid-rs/.gitignore create mode 100644 dom/webauthn/u2f-hid-rs/.travis.yml create mode 100644 dom/webauthn/u2f-hid-rs/Cargo.toml create mode 100644 dom/webauthn/u2f-hid-rs/LICENSE create mode 100644 dom/webauthn/u2f-hid-rs/README.md create mode 100644 dom/webauthn/u2f-hid-rs/build.rs create mode 100644 dom/webauthn/u2f-hid-rs/examples/main.rs create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/.gitignore create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/1172fb09a3c99ed54c7d677faae5386d938d5834 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/3e28ec02ceb803efa105dc82fcfc9cb4014d7ced create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5232969ad85258a14c3fbdb7b3e112539755f3a4 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5814556fab57a61da7d4150c2b45c15686df25ec create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/60c1a897bef5145eb977461d13f789e1ddfa4a69 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/7f960b178bcf3adf082da7c632b4e1366ba22274 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a22d2b1daa9483abc026fa2f75290aa51be59ca9 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a5142fc55a5d72c9978bd54c73c8e32743bd8cfe create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/ab4368e52f60dbe956d28100adf2a4d110b615ec create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/1172fb09a3c99ed54c7d677faae5386d938d5834 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/62b74bc0ad2433f77a007b95eaf964ea719d21e2 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/680d38f668ab4372aaf0d57910c30a5ce6a6a03d create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/f94c8e14f32ca38e773745c5622e2da2a18c3a47 create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read.rs create mode 100644 dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read_write.rs create mode 100644 dom/webauthn/u2f-hid-rs/rustfmt.toml create mode 100644 dom/webauthn/u2f-hid-rs/src/android/mod.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/capi.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/consts.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/lib.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/linux/device.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/linux/devicemap.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/linux/mod.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/linux/monitor.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/macos/device.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/macos/devicemap.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/macos/iohid.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/macos/iokit.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/macos/mod.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/macos/monitor.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/manager.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/runloop.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h create mode 100644 dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/u2ftypes.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/util.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/windows/device.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/windows/devicemap.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/windows/mod.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/windows/monitor.rs create mode 100644 dom/webauthn/u2f-hid-rs/src/windows/winapi.rs diff --git a/dom/webauthn/u2f-hid-rs/.gitignore b/dom/webauthn/u2f-hid-rs/.gitignore new file mode 100644 index 000000000000..97455fc4af67 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/.gitignore @@ -0,0 +1,9 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock + +.DS_Store diff --git a/dom/webauthn/u2f-hid-rs/.travis.yml b/dom/webauthn/u2f-hid-rs/.travis.yml new file mode 100644 index 000000000000..970bb5577491 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/.travis.yml @@ -0,0 +1,30 @@ +sudo: false +language: rust +cache: cargo +rust: + - stable + - beta + - nightly + +addons: + apt: + packages: + - build-essential + - libudev-dev + +before_install: + - pkg-config --list-all + - pkg-config --libs libudev + - pkg-config --modversion libudev + - cargo install rustfmt || true + +script: +- | + if [ "$TRAVIS_RUST_VERSION" == "nightly" ] ; then + export ASAN_OPTIONS="detect_odr_violation=1:leak_check_at_exit=0:detect_leaks=0" + export RUSTFLAGS="-Z sanitizer=address" + fi +- | + cargo fmt -- --write-mode=diff && + cargo build && + cargo test diff --git a/dom/webauthn/u2f-hid-rs/Cargo.toml b/dom/webauthn/u2f-hid-rs/Cargo.toml new file mode 100644 index 000000000000..e657906d40cd --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "u2fhid" +version = "0.1.0" +authors = ["Kyle Machulis ", "J.C. Jones ", "Tim Taubert "] +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" diff --git a/dom/webauthn/u2f-hid-rs/LICENSE b/dom/webauthn/u2f-hid-rs/LICENSE new file mode 100644 index 000000000000..398385c9fb9b --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/LICENSE @@ -0,0 +1,374 @@ + Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + +(a) that the initial Contributor has attached the notice described +in Exhibit B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of +version 1.1 or earlier of the License, but not also under the +terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + +(a) any file in Source Code Form that results from an addition to, +deletion from, or modification of the contents of Covered +Software; or + +(b) any new file in Source Code Form that contains any Covered +Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. + diff --git a/dom/webauthn/u2f-hid-rs/README.md b/dom/webauthn/u2f-hid-rs/README.md new file mode 100644 index 000000000000..2a3e8eae24c6 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/README.md @@ -0,0 +1,50 @@ +# A Rust HID library for interacting with U2F Security Keys + +[![Build Status](https://travis-ci.org/jcjones/u2f-hid-rs.svg?branch=master)](https://travis-ci.org/jcjones/u2f-hid-rs) +![Maturity Level](https://img.shields.io/badge/maturity-beta-yellow.svg) + +This is a cross-platform library for interacting with U2F Security Key-type devices via Rust. + +* **Supported Platforms**: Windows, Linux, and Mac OS X. +* **Supported HID Transports**: USB. +* **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html). + +This library currently focuses on U2F security keys, but is expected to be extended to +support additional protocols and transports. + +## Usage + +There's only a simple example function that tries to register and sign right now. It uses +[env_logger](http://rust-lang-nursery.github.io/log/env_logger/) for logging, which you +configure with the `RUST_LOG` environment variable: + +``` +cargo build +RUST_LOG=debug cargo run --example main +``` + +Proper usage should be to call into this library from something else - e.g., Firefox. There are +some [C headers exposed for the purpose](u2f-hid-rs/blob/master/src/u2fhid-capi.h). + +## Tests + +There are some tests of the cross-platform runloop logic and the protocol decoder: + +``` +cargo test +``` + +## Fuzzing + +There are fuzzers for the USB protocol reader, basically fuzzing inputs from the HID layer. +There are not (yet) fuzzers for the C API used by callers (such as Gecko). + +To fuzz, you will need cargo-fuzz (the latest version from GitHub) as well as Rust Nightly. + +``` +rustup install nightly +cargo install --git https://github.com/rust-fuzz/cargo-fuzz/ + +rustup run nightly cargo fuzz run u2f_read -- -max_len=512 +rustup run nightly cargo fuzz run u2f_read_write -- -max_len=512 +``` diff --git a/dom/webauthn/u2f-hid-rs/build.rs b/dom/webauthn/u2f-hid-rs/build.rs new file mode 100644 index 000000000000..54f038e7fa64 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/build.rs @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +fn main() { + #[cfg(any(target_os = "macos"))] + println!("cargo:rustc-link-lib=framework=IOKit"); +} diff --git a/dom/webauthn/u2f-hid-rs/examples/main.rs b/dom/webauthn/u2f-hid-rs/examples/main.rs new file mode 100644 index 000000000000..565437ffea5f --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/examples/main.rs @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate base64; +extern crate crypto; +extern crate u2fhid; +use crypto::digest::Digest; +use crypto::sha2::Sha256; +use std::io; +use std::sync::mpsc::channel; +use u2fhid::U2FManager; + +extern crate log; +extern crate env_logger; + +fn u2f_get_key_handle_from_register_response(register_response: &Vec) -> io::Result> { + 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 = 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 = 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."); +} diff --git a/dom/webauthn/u2f-hid-rs/fuzz/.gitignore b/dom/webauthn/u2f-hid-rs/fuzz/.gitignore new file mode 100644 index 000000000000..35ae8a00d529 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/fuzz/.gitignore @@ -0,0 +1,2 @@ +target +artifacts diff --git a/dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml b/dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml new file mode 100644 index 000000000000..77c5706e4521 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/fuzz/Cargo.toml @@ -0,0 +1,29 @@ + +[package] +name = "u2fhid-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false + +[package.metadata] +cargo-fuzz = true + +[dependencies] +rand = "0.3" + +[dependencies.u2fhid] +path = ".." +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "u2f_read" +path = "fuzz_targets/u2f_read.rs" + +[[bin]] +name = "u2f_read_write" +path = "fuzz_targets/u2f_read_write.rs" diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/1172fb09a3c99ed54c7d677faae5386d938d5834 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/1172fb09a3c99ed54c7d677faae5386d938d5834 new file mode 100644 index 0000000000000000000000000000000000000000..1ea3c72d74777dd0e45ab394d76bd6ddf15a0331 GIT binary patch literal 64 TcmZS3XV}!tz?9j_Kpp@9iQEEJ literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/3e28ec02ceb803efa105dc82fcfc9cb4014d7ced b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/3e28ec02ceb803efa105dc82fcfc9cb4014d7ced new file mode 100644 index 0000000000000000000000000000000000000000..cfaf37c1a656f666fa294abe13638d0cd1e21feb GIT binary patch literal 64 jcmezW|Ns9s2EnX-k~?lQ&YQ`g&#;M!g^7)kfk*%VAU6q+ literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5232969ad85258a14c3fbdb7b3e112539755f3a4 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5232969ad85258a14c3fbdb7b3e112539755f3a4 new file mode 100644 index 0000000000000000000000000000000000000000..cb8d8b2cbdd9158ae43ec1c56f839ae25ada0347 GIT binary patch literal 140 ecmezW9|9N<;5&i|XJHd#fNK2@SA~l~q(T6bav9eE literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5814556fab57a61da7d4150c2b45c15686df25ec b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/5814556fab57a61da7d4150c2b45c15686df25ec new file mode 100644 index 0000000000000000000000000000000000000000..730031931ce97bbb16d2a9212e32bdf894784a27 GIT binary patch literal 151 jcmezW9|9PFfDuFx1Pc)Wsuw$j-5{7+?6M3DOb`hG-|REO literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/60c1a897bef5145eb977461d13f789e1ddfa4a69 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/60c1a897bef5145eb977461d13f789e1ddfa4a69 new file mode 100644 index 0000000000000000000000000000000000000000..3655d50ce7e463cb5785cd3bb0bee699f572fdb7 GIT binary patch literal 512 zcmZQzKmz}Ppp8K=YoFwf+l=#OGUzjGVq#%pV? osG`L1xIln`eEleP5NQndU?e?YQS1hBn9xN)4nh}0;^R~g06q6g+5i9m literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/7f960b178bcf3adf082da7c632b4e1366ba22274 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/7f960b178bcf3adf082da7c632b4e1366ba22274 new file mode 100644 index 0000000000000000000000000000000000000000..cc0fd3ff81cfa5a77ad67ddfd9cb3b296686408c GIT binary patch literal 512 zcmezW9|9O~0S;^uT-XFj5#s^@QZ#_nHlS)i(~n{zOb$jP>4aJhr63YW%8-~ynyAG@ Yu@$$WU`Jqc2~a;yUASd%YGGgi0PFz?=>Px# literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a22d2b1daa9483abc026fa2f75290aa51be59ca9 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a22d2b1daa9483abc026fa2f75290aa51be59ca9 new file mode 100644 index 0000000000000000000000000000000000000000..57883a2b45edc9aaaefe2f1431d92b74b19a3a5e GIT binary patch literal 226 ocmezW|NnmmVgXPs0$|mIkjBQsss${FO*KLcyFQpac3HS60Nf5#tN;K2 literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a5142fc55a5d72c9978bd54c73c8e32743bd8cfe b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/a5142fc55a5d72c9978bd54c73c8e32743bd8cfe new file mode 100644 index 0000000000000000000000000000000000000000..01cf945a13d09697d562c229cc620643d1400c17 GIT binary patch literal 110 TcmezW9|9OC15i``6EFn;Y&s9| literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/ab4368e52f60dbe956d28100adf2a4d110b615ec b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read/ab4368e52f60dbe956d28100adf2a4d110b615ec new file mode 100644 index 0000000000000000000000000000000000000000..4971b88b4de19beadfe5f865eec085565e23f08d GIT binary patch literal 105 fcmezW|NnmmC}4px;51MY9l+&a4448K9|sKpnx-D+ literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/1172fb09a3c99ed54c7d677faae5386d938d5834 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/1172fb09a3c99ed54c7d677faae5386d938d5834 new file mode 100644 index 0000000000000000000000000000000000000000..1ea3c72d74777dd0e45ab394d76bd6ddf15a0331 GIT binary patch literal 64 TcmZS3XV}!tz?9j_Kpp@9iQEEJ literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/62b74bc0ad2433f77a007b95eaf964ea719d21e2 b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/62b74bc0ad2433f77a007b95eaf964ea719d21e2 new file mode 100644 index 0000000000000000000000000000000000000000..e1f9493e6dc1681e137e3e5be835c51187566fb6 GIT binary patch literal 512 zcmZS3XV}cp%n{0<&#PVHBM1c(vBu$XE{YKlx1&b}iW2-B0D?gzlmGw# literal 0 HcmV?d00001 diff --git a/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/680d38f668ab4372aaf0d57910c30a5ce6a6a03d b/dom/webauthn/u2f-hid-rs/fuzz/corpus/u2f_read_write/680d38f668ab4372aaf0d57910c30a5ce6a6a03d new file mode 100644 index 0000000000000000000000000000000000000000..d15aceb08e6239987939f4b90c37be1e4e8240da GIT binary patch literal 33 jcmZS3XV}cp%n{0<&# { + 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 { + 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 { + 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::(); + let _ = sendrecv(&mut dev, cmd, data); +}); diff --git a/dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read_write.rs b/dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read_write.rs new file mode 100644 index 000000000000..86a802e936a5 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/fuzz/fuzz_targets/u2f_read_write.rs @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate rand; +extern crate u2fhid; + +use rand::{thread_rng, Rng}; +use std::{cmp, io}; + +use u2fhid::{CID_BROADCAST, HID_RPT_SIZE}; +use u2fhid::{U2FDevice, sendrecv}; + +struct TestDevice { + cid: [u8; 4], + data: Vec, +} + +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 { + 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 { + 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::(); + let res = sendrecv(&mut dev, cmd, data); + assert_eq!(data, &res.unwrap()[..]); +}); diff --git a/dom/webauthn/u2f-hid-rs/rustfmt.toml b/dom/webauthn/u2f-hid-rs/rustfmt.toml new file mode 100644 index 000000000000..ed37b0c63a5f --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/rustfmt.toml @@ -0,0 +1,2 @@ +comment_width = 200 +wrap_comments = true diff --git a/dom/webauthn/u2f-hid-rs/src/android/mod.rs b/dom/webauthn/u2f-hid-rs/src/android/mod.rs new file mode 100644 index 000000000000..33d1569aeabc --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/android/mod.rs @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// No-op module to permit compiling token HID support for Android, where +// no results are returned. + +#![allow(dead_code)] + +use util::OnceCallback; + +pub struct PlatformManager {} + +impl PlatformManager { + pub fn new() -> Self { + Self {} + } + + pub fn register( + &mut self, + timeout: u64, + challenge: Vec, + application: Vec, + callback: OnceCallback>, + ) { + // No-op on Android + } + + pub fn sign( + &mut self, + timeout: u64, + challenge: Vec, + application: Vec, + key_handles: Vec>, + callback: OnceCallback<(Vec, Vec)>, + ) { + // No-op on Android + } + + // This blocks. + pub fn cancel(&mut self) { + // No-op on Android + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/capi.rs b/dom/webauthn/u2f-hid-rs/src/capi.rs new file mode 100644 index 000000000000..b6dd171f99ce --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/capi.rs @@ -0,0 +1,197 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use libc::size_t; +use rand::{thread_rng, Rng}; +use std::collections::HashMap; +use std::{ptr, slice}; + +use U2FManager; + +type U2FKeyHandles = Vec>; +type U2FResult = HashMap>; +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::() +} + +unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec { + 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() +} diff --git a/dom/webauthn/u2f-hid-rs/src/consts.rs b/dom/webauthn/u2f-hid-rs/src/consts.rs new file mode 100644 index 000000000000..98d54423d285 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/consts.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Allow dead code in this module, since it's all packet consts anyways. +#![allow(dead_code)] + +pub const HID_RPT_SIZE: usize = 64; +pub const U2FAPDUHEADER_SIZE: usize = 7; +pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff]; +pub const TYPE_MASK: u8 = 0x80; +pub const TYPE_INIT: u8 = 0x80; +pub const TYPE_CONT: u8 = 0x80; + +// Size of data chunk expected in U2F Init USB HID Packets +pub const INIT_DATA_SIZE: usize = HID_RPT_SIZE - 7; +// Size of data chunk expected in U2F Cont USB HID Packets +pub const CONT_DATA_SIZE: usize = HID_RPT_SIZE - 5; + +pub const PARAMETER_SIZE: usize = 32; + +pub const FIDO_USAGE_PAGE: u16 = 0xf1d0; // FIDO alliance HID usage page +pub const FIDO_USAGE_U2FHID: u16 = 0x01; // U2FHID usage for top-level collection +pub const FIDO_USAGE_DATA_IN: u8 = 0x20; // Raw IN data report +pub const FIDO_USAGE_DATA_OUT: u8 = 0x21; // Raw OUT data report + +// General pub constants + +pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation version +pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms +pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms + +// U2FHID native commands +pub const U2FHID_PING: u8 = (TYPE_INIT | 0x01); // Echo data through local processor only +pub const U2FHID_MSG: u8 = (TYPE_INIT | 0x03); // Send U2F message frame +pub const U2FHID_LOCK: u8 = (TYPE_INIT | 0x04); // Send lock channel command +pub const U2FHID_INIT: u8 = (TYPE_INIT | 0x06); // Channel initialization +pub const U2FHID_WINK: u8 = (TYPE_INIT | 0x08); // Send device identification wink +pub const U2FHID_ERROR: u8 = (TYPE_INIT | 0x3f); // Error response + +// U2FHID_MSG commands +pub const U2F_VENDOR_FIRST: u8 = (TYPE_INIT | 0x40); // First vendor defined command +pub const U2F_VENDOR_LAST: u8 = (TYPE_INIT | 0x7f); // Last vendor defined command +pub const U2F_REGISTER: u8 = 0x01; // Registration command +pub const U2F_AUTHENTICATE: u8 = 0x02; // Authenticate/sign command +pub const U2F_VERSION: u8 = 0x03; // Read version string command + +// U2F_REGISTER command defines +pub const U2F_REGISTER_ID: u8 = 0x05; // Version 2 registration identifier +pub const U2F_REGISTER_HASH_ID: u8 = 0x00; // Version 2 hash identintifier + +// U2F_AUTHENTICATE command defines +pub const U2F_REQUEST_USER_PRESENCE: u8 = 0x03; // Verify user presence and sign +pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is registered + +// U2FHID_INIT command defines +pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge +pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command +pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command + +// Low-level error codes. Return as negatives. + +pub const ERR_NONE: u8 = 0x00; // No error +pub const ERR_INVALID_CMD: u8 = 0x01; // Invalid command +pub const ERR_INVALID_PAR: u8 = 0x02; // Invalid parameter +pub const ERR_INVALID_LEN: u8 = 0x03; // Invalid message length +pub const ERR_INVALID_SEQ: u8 = 0x04; // Invalid message sequencing +pub const ERR_MSG_TIMEOUT: u8 = 0x05; // Message has timed out +pub const ERR_CHANNEL_BUSY: u8 = 0x06; // Channel busy +pub const ERR_LOCK_REQUIRED: u8 = 0x0a; // Command requires channel lock +pub const ERR_INVALID_CID: u8 = 0x0b; // Command not allowed on this cid +pub const ERR_OTHER: u8 = 0x7f; // Other unspecified error + +// These are ISO 7816-4 defined response status words. +pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00]; +pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85]; +pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80]; +pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00]; diff --git a/dom/webauthn/u2f-hid-rs/src/lib.rs b/dom/webauthn/u2f-hid-rs/src/lib.rs new file mode 100644 index 000000000000..9ddb0698ca67 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/lib.rs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[macro_use] +mod util; + +#[cfg(any(target_os = "linux"))] +extern crate libudev; + +#[cfg(any(target_os = "linux"))] +#[path = "linux/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "macos"))] +extern crate core_foundation_sys; + +#[cfg(any(target_os = "macos"))] +#[path = "macos/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "windows"))] +#[path = "windows/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "android"))] +#[path = "android/mod.rs"] +pub mod platform; + +#[macro_use] +extern crate log; +extern crate rand; +extern crate libc; +extern crate boxfnonce; + +mod consts; +mod runloop; +mod u2ftypes; +mod u2fprotocol; + +mod manager; +pub use manager::U2FManager; + +mod capi; +pub use capi::*; + +#[cfg(fuzzing)] +pub use u2fprotocol::*; +#[cfg(fuzzing)] +pub use u2ftypes::*; +#[cfg(fuzzing)] +pub use consts::*; diff --git a/dom/webauthn/u2f-hid-rs/src/linux/device.rs b/dom/webauthn/u2f-hid-rs/src/linux/device.rs new file mode 100644 index 000000000000..3a87a6ab7fc7 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/linux/device.rs @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::ffi::{CString, OsString}; +use std::io; +use std::io::{Read, Write}; +use std::os::unix::prelude::*; + +use consts::CID_BROADCAST; +use platform::hidraw; +use util::{from_unix_result, to_io_err}; +use u2ftypes::U2FDevice; + +#[derive(Debug)] +pub struct Device { + path: OsString, + fd: libc::c_int, + cid: [u8; 4], +} + +impl Device { + pub fn new(path: OsString) -> io::Result { + 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 { + 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 { + 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; + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/linux/devicemap.rs b/dom/webauthn/u2f-hid-rs/src/linux/devicemap.rs new file mode 100644 index 000000000000..85bccf9e3509 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/linux/devicemap.rs @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::hash_map::ValuesMut; +use std::collections::HashMap; +use std::ffi::OsString; + +use platform::device::Device; +use platform::monitor::Event; +use u2fprotocol::u2f_init_device; + +pub struct DeviceMap { + map: HashMap, +} + +impl DeviceMap { + pub fn new() -> Self { + Self { map: HashMap::new() } + } + + pub fn values_mut(&mut self) -> ValuesMut { + 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); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs b/dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs new file mode 100644 index 000000000000..ca9fd565c0fc --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/linux/hidraw.rs @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::io; +use std::mem; +use std::os::unix::io::RawFd; + +use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; +use util::{from_unix_result, io_err}; + +#[allow(non_camel_case_types)] +#[repr(C)] +pub struct ReportDescriptor { + size: ::libc::c_int, + value: [u8; 4096], +} + +impl ReportDescriptor { + fn iter(self) -> ReportDescriptorIterator { + ReportDescriptorIterator::new(self) + } +} + +const NRBITS: u32 = 8; +const TYPEBITS: u32 = 8; + +const READ: u8 = 2; +const SIZEBITS: u8 = 14; + +const NRSHIFT: u32 = 0; +const TYPESHIFT: u32 = NRSHIFT + NRBITS as u32; +const SIZESHIFT: u32 = TYPESHIFT + TYPEBITS as u32; +const DIRSHIFT: u32 = SIZESHIFT + SIZEBITS as u32; + +// The 4 MSBs (the tag) are set when it's a long item. +const HID_MASK_LONG_ITEM_TAG: u8 = 0b11110000; +// The 2 LSBs denote the size of a short item. +const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b00000011; +// The 6 MSBs denote the tag (4) and type (2). +const HID_MASK_ITEM_TAGTYPE: u8 = 0b11111100; +// tag=0000, type=10 (local) +const HID_ITEM_TAGTYPE_USAGE: u8 = 0b00001000; +// tag=0000, type=01 (global) +const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b00000100; + +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/hid.h +const HID_MAX_DESCRIPTOR_SIZE: usize = 4096; + +macro_rules! ioctl { + ($dir:expr, $name:ident, $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result { + 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 { + 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::()); + + // 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 { + 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 { + 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 +} diff --git a/dom/webauthn/u2f-hid-rs/src/linux/mod.rs b/dom/webauthn/u2f-hid-rs/src/linux/mod.rs new file mode 100644 index 000000000000..bf633eac0e87 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/linux/mod.rs @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::time::Duration; +use std::thread; + +mod device; +mod devicemap; +mod hidraw; +mod monitor; + +use consts::PARAMETER_SIZE; +use runloop::RunLoop; +use util::{io_err, OnceCallback}; +use u2fprotocol::{u2f_is_keyhandle_valid, u2f_register, u2f_sign}; + +use self::devicemap::DeviceMap; +use self::monitor::Monitor; + +pub struct PlatformManager { + // Handle to the thread loop. + thread: Option, +} + +impl PlatformManager { + pub fn new() -> Self { + Self { thread: None } + } + + pub fn register( + &mut self, + timeout: u64, + challenge: Vec, + application: Vec, + callback: OnceCallback>, + ) { + // 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, + application: Vec, + key_handles: Vec>, + callback: OnceCallback<(Vec, Vec)>, + ) { + // 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(); + } + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/linux/monitor.rs b/dom/webauthn/u2f-hid-rs/src/linux/monitor.rs new file mode 100644 index 000000000000..2c7511cd3065 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/linux/monitor.rs @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use libudev; +use libudev::EventType; + +use libc::{c_int, c_short, c_ulong}; + +use std::ffi::OsString; +use std::io; +use std::os::unix::io::AsRawFd; +use std::sync::mpsc::{channel, Receiver, TryIter}; + +use runloop::RunLoop; +use util::to_io_err; + +const UDEV_SUBSYSTEM: &'static str = "hidraw"; +const POLLIN: c_short = 0x0001; +const POLL_TIMEOUT: c_int = 100; + +fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> { + let nfds = fds.len() as c_ulong; + + let rv = unsafe { ::libc::poll((&mut fds[..]).as_mut_ptr(), nfds, POLL_TIMEOUT) }; + + if rv < 0 { + Err(io::Error::from_raw_os_error(rv)) + } else { + Ok(()) + } +} + +pub enum Event { + Add(OsString), + Remove(OsString), +} + +impl Event { + fn from_udev(event: libudev::Event) -> Option { + 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, + // Handle to the thread loop. + thread: RunLoop, +} + +impl Monitor { + pub fn new() -> io::Result { + 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(); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/macos/device.rs b/dom/webauthn/u2f-hid-rs/src/macos/device.rs new file mode 100644 index 000000000000..4d5c78e9df95 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/macos/device.rs @@ -0,0 +1,192 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; +extern crate log; + +use std::fmt; +use std::io; +use std::io::{Read, Write}; +use std::slice; +use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError}; +use std::time::Duration; + +use core_foundation_sys::base::*; +use libc::c_void; + +use consts::{CID_BROADCAST, HID_RPT_SIZE}; +use u2ftypes::U2FDevice; + +use super::iokit::*; + +const READ_TIMEOUT: u64 = 15; + +pub struct Device { + device_ref: IOHIDDeviceRef, + cid: [u8; 4], + report_rx: Receiver>, + 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>); + 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 { + 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 { + 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>); + + 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); + }; + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/macos/devicemap.rs b/dom/webauthn/u2f-hid-rs/src/macos/devicemap.rs new file mode 100644 index 000000000000..2ab7992954d7 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/macos/devicemap.rs @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::hash_map::ValuesMut; +use std::collections::HashMap; + +use u2fprotocol::u2f_init_device; + +use platform::monitor::Event; +use platform::device::Device; +use platform::iokit::*; + +pub struct DeviceMap { + map: HashMap, +} + +impl DeviceMap { + pub fn new() -> Self { + Self { map: HashMap::new() } + } + + pub fn values_mut(&mut self) -> ValuesMut { + 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); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/macos/iohid.rs b/dom/webauthn/u2f-hid-rs/src/macos/iohid.rs new file mode 100644 index 000000000000..fffd5991b06b --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/macos/iohid.rs @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate log; +extern crate libc; + +use std::io; + +use super::iokit::*; +use core_foundation_sys::base::*; +use core_foundation_sys::dictionary::*; +use core_foundation_sys::number::*; +use core_foundation_sys::runloop::*; +use core_foundation_sys::string::*; + +use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; +use util::io_err; + +pub struct IOHIDDeviceMatcher { + dict: CFDictionaryRef, + keys: Vec, + values: Vec, +} + +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 { + 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"); + } + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs b/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs new file mode 100644 index 000000000000..07d1f1228f44 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] + +extern crate core_foundation_sys; +extern crate libc; + +use libc::c_void; +use core_foundation_sys::base::{CFIndex, CFAllocatorRef}; +use core_foundation_sys::string::CFStringRef; +use core_foundation_sys::runloop::CFRunLoopRef; +use core_foundation_sys::dictionary::CFDictionaryRef; + +type IOOptionBits = u32; + +pub type IOReturn = libc::c_int; + +pub type IOHIDManagerRef = *mut __IOHIDManager; +pub type IOHIDManagerOptions = IOOptionBits; + +pub type IOHIDDeviceCallback = extern "C" fn(context: *mut c_void, + result: IOReturn, + sender: *mut c_void, + device: IOHIDDeviceRef); + +pub type IOHIDReportType = IOOptionBits; +pub type IOHIDReportCallback = extern "C" fn(context: *mut c_void, + result: IOReturn, + sender: *mut c_void, + report_type: IOHIDReportType, + report_id: u32, + report: *mut u8, + report_len: CFIndex); + +pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0; + +pub const kIOHIDReportTypeOutput: IOHIDReportType = 1; + +#[repr(C)] +pub struct __IOHIDManager { + __private: c_void, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct IOHIDDeviceRef(*const c_void); + +unsafe impl Send for IOHIDDeviceRef {} +unsafe impl Sync for IOHIDDeviceRef {} + +extern "C" { + // IOHIDManager + pub fn IOHIDManagerCreate( + allocator: CFAllocatorRef, + options: IOHIDManagerOptions, + ) -> IOHIDManagerRef; + pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef); + pub fn IOHIDManagerRegisterDeviceMatchingCallback( + manager: IOHIDManagerRef, + callback: IOHIDDeviceCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerRegisterDeviceRemovalCallback( + manager: IOHIDManagerRef, + callback: IOHIDDeviceCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; + pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; + pub fn IOHIDManagerScheduleWithRunLoop( + manager: IOHIDManagerRef, + runLoop: CFRunLoopRef, + runLoopMode: CFStringRef, + ); + + // IOHIDDevice + pub fn IOHIDDeviceSetReport( + device: IOHIDDeviceRef, + reportType: IOHIDReportType, + reportID: CFIndex, + report: *const u8, + reportLength: CFIndex, + ) -> IOReturn; + pub fn IOHIDDeviceRegisterInputReportCallback( + device: IOHIDDeviceRef, + report: *const u8, + reportLength: CFIndex, + callback: IOHIDReportCallback, + context: *mut c_void, + ); +} diff --git a/dom/webauthn/u2f-hid-rs/src/macos/mod.rs b/dom/webauthn/u2f-hid-rs/src/macos/mod.rs new file mode 100644 index 000000000000..530e84dda07e --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/macos/mod.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate log; +extern crate libc; + +use std::thread; +use std::time::Duration; + +mod device; +mod devicemap; +mod iokit; +mod iohid; +mod monitor; + +use self::devicemap::DeviceMap; +use self::monitor::Monitor; + +use consts::PARAMETER_SIZE; +use runloop::RunLoop; +use util::{io_err, OnceCallback}; +use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid}; + +#[derive(Default)] +pub struct PlatformManager { + // Handle to the thread loop. + thread: Option, +} + +impl PlatformManager { + pub fn new() -> Self { + Default::default() + } + + pub fn register( + &mut self, + timeout: u64, + challenge: Vec, + application: Vec, + callback: OnceCallback>, + ) { + // 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, + application: Vec, + key_handles: Vec>, + callback: OnceCallback<(Vec, Vec)>, + ) { + // 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(); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs b/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs new file mode 100644 index 000000000000..5d25c91ddfff --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::sync::mpsc::{channel, Sender, Receiver, TryIter}; +use std::thread; + +use super::iohid::*; +use super::iokit::*; +use core_foundation_sys::runloop::*; +use runloop::RunLoop; + +extern crate log; +extern crate libc; +use libc::c_void; + +pub enum Event { + Add(IOHIDDeviceRef), + Remove(IOHIDDeviceRef), +} + +pub struct Monitor { + // Receive events from the thread. + rx: Receiver, + // Handle to the thread loop. + thread: RunLoop, +} + +impl Monitor { + pub fn new() -> io::Result { + 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) }; + + // 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 { + 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) }; + 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) }; + let _ = tx.send(Event::Remove(device)); + } +} + +impl Drop for Monitor { + fn drop(&mut self) { + debug!("OSX Runloop dropped"); + self.thread.cancel(); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/manager.rs b/dom/webauthn/u2f-hid-rs/src/manager.rs new file mode 100644 index 000000000000..5b2688181934 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/manager.rs @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::sync::mpsc::{channel, Sender, RecvTimeoutError}; +use std::time::Duration; + +use consts::PARAMETER_SIZE; +use platform::PlatformManager; +use runloop::RunLoop; +use util::{to_io_err, OnceCallback}; + +pub enum QueueAction { + Register { + timeout: u64, + challenge: Vec, + application: Vec, + callback: OnceCallback>, + }, + Sign { + timeout: u64, + challenge: Vec, + application: Vec, + key_handles: Vec>, + callback: OnceCallback<(Vec, Vec)>, + }, + Cancel, +} + +pub struct U2FManager { + queue: RunLoop, + tx: Sender, +} + +impl U2FManager { + pub fn new() -> io::Result { + 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( + &self, + timeout: u64, + challenge: Vec, + application: Vec, + callback: F, + ) -> io::Result<()> + where + F: FnOnce(io::Result>), + 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( + &self, + timeout: u64, + challenge: Vec, + application: Vec, + key_handles: Vec>, + callback: F, + ) -> io::Result<()> + where + F: FnOnce(io::Result<(Vec, Vec)>), + 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(); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/runloop.rs b/dom/webauthn/u2f-hid-rs/src/runloop.rs new file mode 100644 index 000000000000..39cdcaee3060 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/runloop.rs @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::sync::{Arc, Mutex, Weak}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::thread::JoinHandle; +use std::time::{Duration, Instant}; + +struct Canary { + alive: AtomicBool, + thread: Mutex>>, +} + +impl Canary { + fn new() -> Self { + Self { + alive: AtomicBool::new(true), + thread: Mutex::new(None), + } + } +} + +pub struct RunLoop { + flag: Weak, +} + +impl RunLoop { + pub fn new(fun: F, timeout_ms: u64) -> io::Result + 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 + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h b/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h new file mode 100644 index 000000000000..de2db2c985a5 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __U2FHID_CAPI +#define __U2FHID_CAPI +#include +#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 diff --git a/dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs b/dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs new file mode 100644 index 000000000000..f576e3e33739 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/u2fprotocol.rs @@ -0,0 +1,441 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate std; + +use rand::{thread_rng, Rng}; +use std::io; +use std::io::{Read, Write}; +use std::ffi::CString; + +use consts::*; +use u2ftypes::*; +use util::io_err; + +//////////////////////////////////////////////////////////////////////// +// Device Commands +//////////////////////////////////////////////////////////////////////// + +pub fn u2f_init_device(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(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result> +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( + dev: &mut T, + challenge: &[u8], + application: &[u8], + key_handle: &[u8], +) -> io::Result> +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( + dev: &mut T, + challenge: &[u8], + application: &[u8], + key_handle: &[u8], +) -> io::Result +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(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(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(dev: &mut T) -> io::Result +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(status: [u8; 2], val: T) -> io::Result { + 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(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result> +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(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec, [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 { + // 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 { + 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(); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/u2ftypes.rs b/dom/webauthn/u2f-hid-rs/src/u2ftypes.rs new file mode 100644 index 000000000000..67243e94615f --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/u2ftypes.rs @@ -0,0 +1,188 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{cmp, io}; + +use consts::*; +use util::io_err; + +use log; + +fn trace_hex(data: &[u8]) { + if log_enabled!(log::LogLevel::Trace) { + let parts: Vec = 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(dev: &mut T) -> io::Result> + 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(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result + 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(dev: &mut T, seq: u8, max: usize) -> io::Result> + 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(dev: &mut T, seq: u8, data: &[u8]) -> io::Result + 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> { + 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) + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/util.rs b/dom/webauthn/u2f-hid-rs/src/util.rs new file mode 100644 index 000000000000..2fb8b8f6c520 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/util.rs @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::error::Error; +use std::io; +use std::sync::{Arc, Mutex}; + +use boxfnonce::SendBoxFnOnce; + +macro_rules! try_or { + ($val:expr, $or:expr) => { + match $val { + Ok(v) => { v } + Err(e) => { return $or(e); } + } + } +} + +pub trait Signed { + fn is_negative(&self) -> bool; +} + +impl Signed for i32 { + fn is_negative(&self) -> bool { + *self < (0 as i32) + } +} + +impl Signed for usize { + fn is_negative(&self) -> bool { + (*self as isize) < (0 as isize) + } +} + +#[cfg(any(target_os = "linux"))] +pub fn from_unix_result(rv: T) -> io::Result { + 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(err: T) -> io::Error { + io_err(err.description()) +} + +pub struct OnceCallback { + callback: Arc,)>>>>, +} + +impl OnceCallback { + pub fn new(cb: F) -> Self + where + F: FnOnce(io::Result), + F: Send + 'static, + { + let cb = Some(SendBoxFnOnce::from(cb)); + Self { callback: Arc::new(Mutex::new(cb)) } + } + + pub fn call(&self, rv: io::Result) { + if let Ok(mut cb) = self.callback.lock() { + if let Some(cb) = cb.take() { + cb.call(rv); + } + } + } +} + +impl Clone for OnceCallback { + fn clone(&self) -> Self { + Self { callback: self.callback.clone() } + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/windows/device.rs b/dom/webauthn/u2f-hid-rs/src/windows/device.rs new file mode 100644 index 000000000000..707e5eac584d --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/windows/device.rs @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::{Read, Write}; +use std::os::windows::io::AsRawHandle; + +use consts::{CID_BROADCAST, HID_RPT_SIZE, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; +use super::winapi::DeviceCapabilities; + +use u2ftypes::U2FDevice; + +#[derive(Debug)] +pub struct Device { + path: String, + file: File, + cid: [u8; 4], +} + +impl Device { + pub fn new(path: String) -> io::Result { + 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 { + // 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 { + 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; + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/windows/devicemap.rs b/dom/webauthn/u2f-hid-rs/src/windows/devicemap.rs new file mode 100644 index 000000000000..96c222fab970 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/windows/devicemap.rs @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::hash_map::ValuesMut; +use std::collections::HashMap; + +use platform::device::Device; +use platform::monitor::Event; +use u2fprotocol::u2f_init_device; + +pub struct DeviceMap { + map: HashMap, +} + +impl DeviceMap { + pub fn new() -> Self { + Self { map: HashMap::new() } + } + + pub fn values_mut(&mut self) -> ValuesMut { + 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); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/windows/mod.rs b/dom/webauthn/u2f-hid-rs/src/windows/mod.rs new file mode 100644 index 000000000000..728bf27bcb9e --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/windows/mod.rs @@ -0,0 +1,157 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::thread; +use std::time::Duration; + +mod device; +mod devicemap; +mod monitor; +mod winapi; + +use consts::PARAMETER_SIZE; +use runloop::RunLoop; +use util::{io_err, OnceCallback}; +use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid}; + +use self::devicemap::DeviceMap; +use self::monitor::Monitor; + +pub struct PlatformManager { + // Handle to the thread loop. + thread: Option, +} + +impl PlatformManager { + pub fn new() -> Self { + Self { thread: None } + } + + pub fn register( + &mut self, + timeout: u64, + challenge: Vec, + application: Vec, + callback: OnceCallback>, + ) { + // 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, + application: Vec, + key_handles: Vec>, + callback: OnceCallback<(Vec, Vec)>, + ) { + // 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(); + } + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/windows/monitor.rs b/dom/webauthn/u2f-hid-rs/src/windows/monitor.rs new file mode 100644 index 000000000000..38a4879df388 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/windows/monitor.rs @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashSet; +use std::error::Error; +use std::io; +use std::iter::FromIterator; +use std::sync::mpsc::{channel, Receiver, TryIter}; +use std::thread; +use std::time::Duration; + +use runloop::RunLoop; +use super::winapi::DeviceInfoSet; + +pub fn io_err(msg: &str) -> io::Error { + io::Error::new(io::ErrorKind::Other, msg) +} + +pub fn to_io_err(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, + // Handle to the thread loop. + thread: RunLoop, +} + +impl Monitor { + pub fn new() -> io::Result { + 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(); + } +} diff --git a/dom/webauthn/u2f-hid-rs/src/windows/winapi.rs b/dom/webauthn/u2f-hid-rs/src/windows/winapi.rs new file mode 100644 index 000000000000..6c8b87424725 --- /dev/null +++ b/dom/webauthn/u2f-hid-rs/src/windows/winapi.rs @@ -0,0 +1,263 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::mem; +use std::ptr; +use std::slice; + +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; + +use util::io_err; + +extern crate libc; +extern crate winapi; +use self::winapi::*; + +#[link(name = "setupapi")] +extern "stdcall" { + fn SetupDiGetClassDevsW( + ClassGuid: *const GUID, + Enumerator: PCSTR, + hwndParent: HWND, + flags: DWORD, + ) -> HDEVINFO; + + fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: HDEVINFO) -> BOOL; + + fn SetupDiEnumDeviceInterfaces( + DeviceInfoSet: HDEVINFO, + DeviceInfoData: PSP_DEVINFO_DATA, + InterfaceClassGuid: *const GUID, + MemberIndex: DWORD, + DeviceInterfaceData: PSP_DEVICE_INTERFACE_DATA, + ) -> BOOL; + + fn SetupDiGetDeviceInterfaceDetailW( + DeviceInfoSet: HDEVINFO, + DeviceInterfaceData: PSP_DEVICE_INTERFACE_DATA, + DeviceInterfaceDetailData: PSP_DEVICE_INTERFACE_DETAIL_DATA_W, + DeviceInterfaceDetailDataSize: DWORD, + RequiredSize: PDWORD, + DeviceInfoData: PSP_DEVINFO_DATA, + ) -> BOOL; +} + +#[link(name = "hid")] +extern "stdcall" { + fn HidD_GetPreparsedData( + HidDeviceObject: HANDLE, + PreparsedData: *mut PHIDP_PREPARSED_DATA, + ) -> BOOLEAN; + + fn HidD_FreePreparsedData(PreparsedData: PHIDP_PREPARSED_DATA) -> BOOLEAN; + + fn HidP_GetCaps(PreparsedData: PHIDP_PREPARSED_DATA, Capabilities: PHIDP_CAPS) -> NTSTATUS; +} + +macro_rules! offset_of { + ($ty:ty, $field:ident) => { + unsafe { &(*(0 as *const $ty)).$field as *const _ as usize } + } +} + +fn from_wide_ptr(ptr: *const u16, len: usize) -> String { + assert!(!ptr.is_null() && len % 2 == 0); + let slice = unsafe { slice::from_raw_parts(ptr, len / 2) }; + OsString::from_wide(slice).to_string_lossy().into_owned() +} + +pub struct DeviceInfoSet { + set: HDEVINFO, +} + +impl DeviceInfoSet { + pub fn new() -> io::Result { + 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 { + let mut device_interface_data = unsafe { mem::uninitialized::() }; + device_interface_data.cbSize = mem::size_of::() 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 { + let mut cb_size = mem::size_of::(); + 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 { + 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 + } +}