diff options
-rw-r--r-- | lazy-static/.gitignore | 4 | ||||
-rw-r--r-- | lazy-static/.travis.yml | 34 | ||||
-rw-r--r-- | lazy-static/Cargo.toml | 34 | ||||
-rw-r--r-- | lazy-static/LICENSE-APACHE | 201 | ||||
-rw-r--r-- | lazy-static/LICENSE-MIT | 25 | ||||
-rw-r--r-- | lazy-static/README.md | 79 | ||||
-rw-r--r-- | lazy-static/appveyor.yml | 61 | ||||
-rw-r--r-- | lazy-static/compiletest/Cargo.toml | 11 | ||||
-rw-r--r-- | lazy-static/compiletest/src/lib.rs | 11 | ||||
-rw-r--r-- | lazy-static/compiletest/tests/compile-fail/README.md | 22 | ||||
-rw-r--r-- | lazy-static/compiletest/tests/compile-fail/incorrect_visibility_restriction.rs | 10 | ||||
-rw-r--r-- | lazy-static/compiletest/tests/compile-fail/static_is_private.rs | 14 | ||||
-rw-r--r-- | lazy-static/compiletest/tests/compile-fail/static_is_sized.rs | 11 | ||||
-rw-r--r-- | lazy-static/compiletest/tests/compile_tests.rs | 19 | ||||
-rw-r--r-- | lazy-static/src/core_lazy.rs | 31 | ||||
-rw-r--r-- | lazy-static/src/inline_lazy.rs | 65 | ||||
-rw-r--r-- | lazy-static/src/lib.rs | 212 | ||||
-rw-r--r-- | lazy-static/tests/no_std.rs | 20 | ||||
-rw-r--r-- | lazy-static/tests/test.rs | 162 | ||||
-rw-r--r-- | nitrocli/CHANGELOG.md | 4 | ||||
-rw-r--r-- | nitrocli/Cargo.lock | 19 | ||||
-rw-r--r-- | nitrocli/Cargo.toml | 5 | ||||
-rw-r--r-- | nitrocli/src/commands.rs | 45 | ||||
-rw-r--r-- | nitrocli/src/pinentry.rs | 8 | ||||
-rw-r--r-- | nitrocli/src/tests/config.rs | 12 | ||||
-rw-r--r-- | nitrocli/src/tests/encrypted.rs | 37 | ||||
-rw-r--r-- | nitrocli/src/tests/hidden.rs | 25 | ||||
-rw-r--r-- | nitrocli/src/tests/lock.rs | 15 | ||||
-rw-r--r-- | nitrocli/src/tests/mod.rs | 13 | ||||
-rw-r--r-- | nitrocli/src/tests/otp.rs | 28 | ||||
-rw-r--r-- | nitrocli/src/tests/pin.rs | 61 | ||||
-rw-r--r-- | nitrocli/src/tests/pws.rs | 20 | ||||
-rw-r--r-- | nitrocli/src/tests/reset.rs | 34 | ||||
-rw-r--r-- | nitrocli/src/tests/status.rs | 16 | ||||
-rw-r--r-- | nitrocli/src/tests/unencrypted.rs | 25 | ||||
-rw-r--r-- | nitrokey-sys/CHANGELOG.md | 26 | ||||
-rw-r--r-- | nitrokey-sys/Cargo.toml | 2 | ||||
-rw-r--r-- | nitrokey-sys/build.rs | 4 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/DeviceCommunicationExceptions.cpp (renamed from nitrokey-sys/libnitrokey-v3.4.1/DeviceCommunicationExceptions.cpp) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/LICENSE (renamed from nitrokey-sys/libnitrokey-v3.4.1/LICENSE) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/NK_C_API.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.cc) | 165 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/NK_C_API.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.h) | 230 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/NitrokeyManager.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/NitrokeyManager.cc) | 92 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/README.md (renamed from nitrokey-sys/libnitrokey-v3.4.1/README.md) | 103 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/command_id.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/command_id.cc) | 4 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/device.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/device.cc) | 66 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/CommandFailedException.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/CommandFailedException.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/DeviceCommunicationExceptions.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/DeviceCommunicationExceptions.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/LibraryException.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LibraryException.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/LongOperationInProgressException.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LongOperationInProgressException.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/NitrokeyManager.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/NitrokeyManager.h) | 11 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/command.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/command_id.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command_id.h) | 2 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/cxx_semantics.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/cxx_semantics.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/deprecated.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/deprecated.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/device.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device.h) | 57 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/device_proto.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device_proto.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/dissect.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/dissect.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/hidapi/hidapi.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/hidapi/hidapi.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/log.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/log.h) | 1 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/misc.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/misc.h) | 41 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick10_commands.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands.h) | 40 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick10_commands_0.8.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands_0.8.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick20_commands.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick20_commands.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/libnitrokey/version.h (renamed from nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/version.h) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/log.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/log.cc) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/misc.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/misc.cc) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/version.cc (renamed from nitrokey-sys/libnitrokey-v3.4.1/version.cc) | 0 | ||||
-rw-r--r-- | nitrokey-sys/libnitrokey-v3.5/version.cc.in (renamed from nitrokey-sys/libnitrokey-v3.4.1/version.cc.in) | 0 | ||||
-rw-r--r-- | nitrokey-sys/src/ffi.rs | 377 | ||||
-rw-r--r-- | nitrokey/CHANGELOG.md | 10 | ||||
-rw-r--r-- | nitrokey/Cargo.toml | 8 | ||||
-rw-r--r-- | nitrokey/README.md | 13 | ||||
-rw-r--r-- | nitrokey/TODO.md | 8 | ||||
-rw-r--r-- | nitrokey/src/auth.rs | 90 | ||||
-rw-r--r-- | nitrokey/src/device.rs | 368 | ||||
-rw-r--r-- | nitrokey/src/error.rs | 30 | ||||
-rw-r--r-- | nitrokey/src/lib.rs | 301 | ||||
-rw-r--r-- | nitrokey/src/otp.rs | 27 | ||||
-rw-r--r-- | nitrokey/src/pws.rs | 54 | ||||
-rw-r--r-- | nitrokey/tests/device.rs | 48 | ||||
-rw-r--r-- | nitrokey/tests/lib.rs | 16 | ||||
-rw-r--r-- | nitrokey/tests/otp.rs | 4 | ||||
-rw-r--r-- | nitrokey/tests/pws.rs | 4 |
84 files changed, 3028 insertions, 567 deletions
diff --git a/lazy-static/.gitignore b/lazy-static/.gitignore new file mode 100644 index 0000000..bde55ce --- /dev/null +++ b/lazy-static/.gitignore @@ -0,0 +1,4 @@ +target +doc +Cargo.lock +.cargo diff --git a/lazy-static/.travis.yml b/lazy-static/.travis.yml new file mode 100644 index 0000000..f4c3c74 --- /dev/null +++ b/lazy-static/.travis.yml @@ -0,0 +1,34 @@ +language: rust +matrix: + include: + - rust: 1.24.1 + - rust: stable + script: + - cargo test + - cargo test --features spin_no_std + - os: osx + - rust: beta + - rust: nightly + script: + - cargo test + - cargo bench + - cargo test --features spin_no_std + - cargo bench --features spin_no_std + - cd compiletest + - cargo clean + - cargo test + - cd ../ + + - rust: nightly + before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + script: + - cargo doc --no-deps --all-features + after_success: + - travis-cargo --only nightly doc-upload +script: + - cargo test + +env: + global: + - secure: YXu24LptjeYirjWYjWGsMT2m3mB7LvQATE6TVo7VEUXv8GYoy2ORIHD83PeImxC93MmZ01QeUezRzuCW51ZcK92VnNSBttlF60SvIX18VsJrV92tsAhievFstqYQ+fB8DIuQ8noU0jPz7GpI+R9dlTRSImAqWOnVIghA+Wzz7Js= diff --git a/lazy-static/Cargo.toml b/lazy-static/Cargo.toml new file mode 100644 index 0000000..679f5cd --- /dev/null +++ b/lazy-static/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "lazy_static" +# NB: When modifying, also modify html_root_url in lib.rs +version = "1.2.0" +authors = ["Marvin Löbel <loebel.marvin@gmail.com>"] +license = "MIT/Apache-2.0" + +description = "A macro for declaring lazily evaluated statics in Rust." +readme = "README.md" +documentation = "https://docs.rs/lazy_static" + +repository = "https://github.com/rust-lang-nursery/lazy-static.rs" +keywords = ["macro", "lazy", "static"] +categories = [ "no-std", "rust-patterns", "memory-management" ] +exclude = ["/.travis.yml", "/appveyor.yml"] + +[dependencies.spin] +version = "0.4.10" +optional = true +default-features = false +features = ["once"] + +[features] +nightly = [] +spin_no_std = ["spin"] + +[badges] +appveyor = { repository = "rust-lang-nursery/lazy-static.rs" } +travis-ci = { repository = "rust-lang-nursery/lazy-static.rs" } + +is-it-maintained-issue-resolution = { repository = "rust-lang-nursery/lazy-static.rs" } +is-it-maintained-open-issues = { repository = "rust-lang-nursery/lazy-static.rs" } + +maintenance = { status = "passively-maintained" } diff --git a/lazy-static/LICENSE-APACHE b/lazy-static/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/lazy-static/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/lazy-static/LICENSE-MIT b/lazy-static/LICENSE-MIT new file mode 100644 index 0000000..25597d5 --- /dev/null +++ b/lazy-static/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2010 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/lazy-static/README.md b/lazy-static/README.md new file mode 100644 index 0000000..d96cdf8 --- /dev/null +++ b/lazy-static/README.md @@ -0,0 +1,79 @@ +lazy-static.rs +============== + +A macro for declaring lazily evaluated statics in Rust. + +Using this macro, it is possible to have `static`s that require code to be +executed at runtime in order to be initialized. +This includes anything requiring heap allocations, like vectors or hash maps, +as well as anything that requires non-const function calls to be computed. + +[![Travis-CI Status](https://travis-ci.org/rust-lang-nursery/lazy-static.rs.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/lazy-static.rs) +[![Latest version](https://img.shields.io/crates/v/lazy_static.svg)](https://crates.io/crates/lazy_static) +[![Documentation](https://docs.rs/lazy_static/badge.svg)](https://docs.rs/lazy_static) +[![License](https://img.shields.io/crates/l/lazy_static.svg)](https://github.com/rust-lang-nursery/lazy-static.rs#license) + +## Minimum supported `rustc` + +`1.24.1+` + +This version is explicitly tested in CI and may only be bumped in new minor versions. Any changes to the supported minimum version will be called out in the release notes. + + +# Getting Started + +[lazy-static.rs is available on crates.io](https://crates.io/crates/lazy_static). +It is recommended to look there for the newest released version, as well as links to the newest builds of the docs. + +At the point of the last update of this README, the latest published version could be used like this: + +Add the following dependency to your Cargo manifest... + +```toml +[dependencies] +lazy_static = "1.2.0" +``` + +...and see the [docs](https://docs.rs/lazy_static) for how to use it. + +# Example + +```rust +#[macro_use] +extern crate lazy_static; + +use std::collections::HashMap; + +lazy_static! { + static ref HASHMAP: HashMap<u32, &'static str> = { + let mut m = HashMap::new(); + m.insert(0, "foo"); + m.insert(1, "bar"); + m.insert(2, "baz"); + m + }; +} + +fn main() { + // First access to `HASHMAP` initializes it + println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap()); + + // Any further access to `HASHMAP` just returns the computed value + println!("The entry for `1` is \"{}\".", HASHMAP.get(&1).unwrap()); +} +``` + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/lazy-static/appveyor.yml b/lazy-static/appveyor.yml new file mode 100644 index 0000000..b138452 --- /dev/null +++ b/lazy-static/appveyor.yml @@ -0,0 +1,61 @@ +environment: + global: + PROJECT_NAME: lazy_static + # When this was added there were revocation check failures when using the + # libcurl backend as libcurl checks by default, but rustup doesn't provide the + # switch to turn this off. Switch to Hyper which looks to not check for + # revocation by default like libcurl does. + RUSTUP_USE_REQWEST: 1 + CARGO_HTTP_CHECK_REVOKE: false + matrix: + # Stable channel + - TARGET: i686-pc-windows-gnu + CHANNEL: stable + - TARGET: i686-pc-windows-msvc + CHANNEL: stable + - TARGET: x86_64-pc-windows-gnu + CHANNEL: stable + - TARGET: x86_64-pc-windows-msvc + CHANNEL: stable + # Beta channel + - TARGET: i686-pc-windows-gnu + CHANNEL: beta + - TARGET: i686-pc-windows-msvc + CHANNEL: beta + - TARGET: x86_64-pc-windows-gnu + CHANNEL: beta + - TARGET: x86_64-pc-windows-msvc + CHANNEL: beta + # Nightly channel + - TARGET: i686-pc-windows-gnu + CHANNEL: nightly + - TARGET: i686-pc-windows-msvc + CHANNEL: nightly + - TARGET: x86_64-pc-windows-gnu + CHANNEL: nightly + - TARGET: x86_64-pc-windows-msvc + CHANNEL: nightly + +# Install Rust and Cargo +# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) +install: + - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init.exe -y --default-toolchain %CHANNEL% --default-host %TARGET% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - if "%TARGET%" == "i686-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw32\bin + - if "%TARGET%" == "x86_64-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw64\bin + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo build --verbose + - cargo test + - if [%CHANNEL%]==[nightly] ( + cd compiletest && + cargo clean && + cargo build --verbose && + cargo test && + cd ../ + ) diff --git a/lazy-static/compiletest/Cargo.toml b/lazy-static/compiletest/Cargo.toml new file mode 100644 index 0000000..e25d5a7 --- /dev/null +++ b/lazy-static/compiletest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lazy_static_compiletest" +version = "0.0.1" +publish = false +authors = ["lazy_static contributors"] + +[dependencies.lazy_static] +path = "../" + +[dependencies.compiletest_rs] +version = "0.3" diff --git a/lazy-static/compiletest/src/lib.rs b/lazy-static/compiletest/src/lib.rs new file mode 100644 index 0000000..0452765 --- /dev/null +++ b/lazy-static/compiletest/src/lib.rs @@ -0,0 +1,11 @@ +/* +This library is a shim around `lazy_static` that disambiguates it with the `lazy_static` +that's shipped with the Rust toolchain. We re-export the entire public API of `lazy_static` +under a different crate name so that can be imported in the compile tests. + +This currently appears to use the right local build of `lazy_static`. +*/ + +extern crate lazy_static; + +pub use self::lazy_static::*; diff --git a/lazy-static/compiletest/tests/compile-fail/README.md b/lazy-static/compiletest/tests/compile-fail/README.md new file mode 100644 index 0000000..58dbc3b --- /dev/null +++ b/lazy-static/compiletest/tests/compile-fail/README.md @@ -0,0 +1,22 @@ +This directory contains snippets of code that should yield a +warning/note/help/error at compilation. Syntax of annotations is described in +[rust documentation](https://github.com/rust-lang/rust/blob/master/src/test/COMPILER_TESTS.md). +For more information check out [`compiletest` crate](https://github.com/laumann/compiletest-rs). + +To run compile tests issue `cargo +nightly --test`. + +## Notes on working with `compiletest` crate + +* Currently code that is inside macro should not be annotated, as `compiletest` + crate cannot deal with the fact that macro invocations effectively changes + line numbering. To prevent this add a `// error-pattern:<your error message here>` + on the top of the file and make sure that you set `deny` lint level + if you want to test compiler message different than error. +* `compiletest` crate by default sets `allow(dead_code)` lint level so make sure + that you change it to something suiting your needs even if the warning is + issued prior to any macro invocation. +* If you get a message `error: 0 unexpected errors found, 1 expected errors not found` + despite the fact that some error was bound to occur don't worry - it's a known + issue in the `compiletest` crate and your error was probably not registered - + make sure that your annotations are correct and that you are setting correct + lint levels. diff --git a/lazy-static/compiletest/tests/compile-fail/incorrect_visibility_restriction.rs b/lazy-static/compiletest/tests/compile-fail/incorrect_visibility_restriction.rs new file mode 100644 index 0000000..360e23d --- /dev/null +++ b/lazy-static/compiletest/tests/compile-fail/incorrect_visibility_restriction.rs @@ -0,0 +1,10 @@ +// incorrect visibility restriction +#[macro_use] +extern crate lazy_static_compiletest as lazy_static; + +lazy_static! { + pub(nonsense) static ref WRONG: () = (); + //~^ ERROR incorrect visibility restriction +} + +fn main() { } diff --git a/lazy-static/compiletest/tests/compile-fail/static_is_private.rs b/lazy-static/compiletest/tests/compile-fail/static_is_private.rs new file mode 100644 index 0000000..6ebc8f5 --- /dev/null +++ b/lazy-static/compiletest/tests/compile-fail/static_is_private.rs @@ -0,0 +1,14 @@ +#[macro_use] +extern crate lazy_static_compiletest as lazy_static; + +mod outer { + pub mod inner { + lazy_static! { + pub(in outer) static ref FOO: () = (); + } + } +} + +fn main() { + assert_eq!(*outer::inner::FOO, ()); //~ ERROR static `FOO` is private +} diff --git a/lazy-static/compiletest/tests/compile-fail/static_is_sized.rs b/lazy-static/compiletest/tests/compile-fail/static_is_sized.rs new file mode 100644 index 0000000..ac1cad4 --- /dev/null +++ b/lazy-static/compiletest/tests/compile-fail/static_is_sized.rs @@ -0,0 +1,11 @@ +// error-pattern:the size for values of type `str` cannot be known at compilation time +#[macro_use] +extern crate lazy_static_compiletest as lazy_static; + +lazy_static! { + pub static ref FOO: str = panic!(); +} + + +fn main() { +} diff --git a/lazy-static/compiletest/tests/compile_tests.rs b/lazy-static/compiletest/tests/compile_tests.rs new file mode 100644 index 0000000..d908077 --- /dev/null +++ b/lazy-static/compiletest/tests/compile_tests.rs @@ -0,0 +1,19 @@ +extern crate compiletest_rs as compiletest; + +fn run_mode(mode: &'static str) { + let mut config = compiletest::Config::default(); + config.mode = mode.parse().expect("Invalid mode"); + config.src_base = ["tests", mode].iter().collect(); + + config.verbose = true; + + config.target_rustcflags = Some("-L target/debug/ -L target/debug/deps/".to_owned()); + config.clean_rmeta(); + + compiletest::run_tests(&config); +} + +#[test] +fn compile_test() { + run_mode("compile-fail"); +} diff --git a/lazy-static/src/core_lazy.rs b/lazy-static/src/core_lazy.rs new file mode 100644 index 0000000..b66c3e0 --- /dev/null +++ b/lazy-static/src/core_lazy.rs @@ -0,0 +1,31 @@ +// Copyright 2016 lazy-static.rs Developers +// +// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or +// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or +// http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +extern crate spin; + +use self::spin::Once; + +pub struct Lazy<T: Sync>(Once<T>); + +impl<T: Sync> Lazy<T> { + pub const INIT: Self = Lazy(Once::INIT); + + #[inline(always)] + pub fn get<F>(&'static self, builder: F) -> &T + where F: FnOnce() -> T + { + self.0.call_once(builder) + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __lazy_static_create { + ($NAME:ident, $T:ty) => { + static $NAME: $crate::lazy::Lazy<$T> = $crate::lazy::Lazy::INIT; + } +} diff --git a/lazy-static/src/inline_lazy.rs b/lazy-static/src/inline_lazy.rs new file mode 100644 index 0000000..268dd45 --- /dev/null +++ b/lazy-static/src/inline_lazy.rs @@ -0,0 +1,65 @@ +// Copyright 2016 lazy-static.rs Developers +// +// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or +// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or +// http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +extern crate core; +extern crate std; + +use self::std::prelude::v1::*; +use self::std::cell::Cell; +use self::std::sync::Once; +pub use self::std::sync::ONCE_INIT; + +// FIXME: Replace Option<T> with MaybeInitialized<T> +pub struct Lazy<T: Sync>(Cell<Option<T>>, Once); + +impl<T: Sync> Lazy<T> { + pub const INIT: Self = Lazy(Cell::new(None), ONCE_INIT); + + #[inline(always)] + pub fn get<F>(&'static self, f: F) -> &T + where + F: FnOnce() -> T, + { + self.1.call_once(|| { + self.0.set(Some(f())); + }); + + // `self.0` is guaranteed to be `Some` by this point + // The `Once` will catch and propegate panics + unsafe { + match *self.0.as_ptr() { + Some(ref x) => x, + None => { + debug_assert!(false, "attempted to derefence an uninitialized lazy static. This is a bug"); + + unreachable_unchecked() + }, + } + } + } +} + +unsafe impl<T: Sync> Sync for Lazy<T> {} + +#[macro_export] +#[doc(hidden)] +macro_rules! __lazy_static_create { + ($NAME:ident, $T:ty) => { + static $NAME: $crate::lazy::Lazy<$T> = $crate::lazy::Lazy::INIT; + }; +} + +/// Polyfill for std::hint::unreachable_unchecked. There currently exists a +/// [crate](https://docs.rs/unreachable) for an equivalent to std::hint::unreachable_unchecked, but +/// lazy_static currently doesn't include any runtime dependencies and we've chosen to include this +/// short polyfill rather than include a new crate in every consumer's build. +/// +/// This should be replaced by std's version when lazy_static starts to require at least Rust 1.27. +unsafe fn unreachable_unchecked() -> ! { + enum Void {} + match std::mem::uninitialized::<Void>() {} +} diff --git a/lazy-static/src/lib.rs b/lazy-static/src/lib.rs new file mode 100644 index 0000000..42dc405 --- /dev/null +++ b/lazy-static/src/lib.rs @@ -0,0 +1,212 @@ +// Copyright 2016 lazy-static.rs Developers +// +// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or +// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or +// http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +/*! +A macro for declaring lazily evaluated statics. + +Using this macro, it is possible to have `static`s that require code to be +executed at runtime in order to be initialized. +This includes anything requiring heap allocations, like vectors or hash maps, +as well as anything that requires function calls to be computed. + +# Syntax + +```ignore +lazy_static! { + [pub] static ref NAME_1: TYPE_1 = EXPR_1; + [pub] static ref NAME_2: TYPE_2 = EXPR_2; + ... + [pub] static ref NAME_N: TYPE_N = EXPR_N; +} +``` + +Attributes (including doc comments) are supported as well: + +```rust +# #[macro_use] +# extern crate lazy_static; +# fn main() { +lazy_static! { + /// This is an example for using doc comment attributes + static ref EXAMPLE: u8 = 42; +} +# } +``` + +# Semantics + +For a given `static ref NAME: TYPE = EXPR;`, the macro generates a unique type that +implements `Deref<TYPE>` and stores it in a static with name `NAME`. (Attributes end up +attaching to this type.) + +On first deref, `EXPR` gets evaluated and stored internally, such that all further derefs +can return a reference to the same object. Note that this can lead to deadlocks +if you have multiple lazy statics that depend on each other in their initialization. + +Apart from the lazy initialization, the resulting "static ref" variables +have generally the same properties as regular "static" variables: + +- Any type in them needs to fulfill the `Sync` trait. +- If the type has a destructor, then it will not run when the process exits. + +# Example + +Using the macro: + +```rust +#[macro_use] +extern crate lazy_static; + +use std::collections::HashMap; + +lazy_static! { + static ref HASHMAP: HashMap<u32, &'static str> = { + let mut m = HashMap::new(); + m.insert(0, "foo"); + m.insert(1, "bar"); + m.insert(2, "baz"); + m + }; + static ref COUNT: usize = HASHMAP.len(); + static ref NUMBER: u32 = times_two(21); +} + +fn times_two(n: u32) -> u32 { n * 2 } + +fn main() { + println!("The map has {} entries.", *COUNT); + println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap()); + println!("A expensive calculation on a static results in: {}.", *NUMBER); +} +``` + +# Implementation details + +The `Deref` implementation uses a hidden static variable that is guarded by an atomic check on each access. + +# Cargo features + +This crate provides two cargo features: + +- `nightly`: This uses unstable language features only available on the nightly release channel for a more optimal implementation. In practice this currently means avoiding a heap allocation per static. This feature might get deprecated at a later point once all relevant optimizations are usable from stable. +- `spin_no_std` (implies `nightly`): This allows using this crate in a no-std environment, by depending on the standalone `spin` crate. + +Both features depend on unstable language features, which means +no guarantees can be made about them in regard to SemVer stability. + +*/ + +#![doc(html_root_url = "https://docs.rs/lazy_static/1.2.0")] +#![no_std] + +#[cfg(not(feature = "spin_no_std"))] +#[path="inline_lazy.rs"] +#[doc(hidden)] +pub mod lazy; + +#[cfg(feature = "spin_no_std")] +#[path="core_lazy.rs"] +#[doc(hidden)] +pub mod lazy; + +#[doc(hidden)] +pub use core::ops::Deref as __Deref; + +#[macro_export(local_inner_macros)] +#[doc(hidden)] +macro_rules! __lazy_static_internal { + // optional visibility restrictions are wrapped in `()` to allow for + // explicitly passing otherwise implicit information about private items + ($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + __lazy_static_internal!(@MAKE TY, $(#[$attr])*, ($($vis)*), $N); + __lazy_static_internal!(@TAIL, $N : $T = $e); + lazy_static!($($t)*); + }; + (@TAIL, $N:ident : $T:ty = $e:expr) => { + impl $crate::__Deref for $N { + type Target = $T; + fn deref(&self) -> &$T { + #[inline(always)] + fn __static_ref_initialize() -> $T { $e } + + #[inline(always)] + fn __stability() -> &'static $T { + __lazy_static_create!(LAZY, $T); + LAZY.get(__static_ref_initialize) + } + __stability() + } + } + impl $crate::LazyStatic for $N { + fn initialize(lazy: &Self) { + let _ = &**lazy; + } + } + }; + // `vis` is wrapped in `()` to prevent parsing ambiguity + (@MAKE TY, $(#[$attr:meta])*, ($($vis:tt)*), $N:ident) => { + #[allow(missing_copy_implementations)] + #[allow(non_camel_case_types)] + #[allow(dead_code)] + $(#[$attr])* + $($vis)* struct $N {__private_field: ()} + #[doc(hidden)] + $($vis)* static $N: $N = $N {__private_field: ()}; + }; + () => () +} + +#[macro_export(local_inner_macros)] +macro_rules! lazy_static { + ($(#[$attr:meta])* static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + // use `()` to explicitly forward the information about private items + __lazy_static_internal!($(#[$attr])* () static ref $N : $T = $e; $($t)*); + }; + ($(#[$attr:meta])* pub static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + __lazy_static_internal!($(#[$attr])* (pub) static ref $N : $T = $e; $($t)*); + }; + ($(#[$attr:meta])* pub ($($vis:tt)+) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + __lazy_static_internal!($(#[$attr])* (pub ($($vis)+)) static ref $N : $T = $e; $($t)*); + }; + () => () +} + +/// Support trait for enabling a few common operation on lazy static values. +/// +/// This is implemented by each defined lazy static, and +/// used by the free functions in this crate. +pub trait LazyStatic { + #[doc(hidden)] + fn initialize(lazy: &Self); +} + +/// Takes a shared reference to a lazy static and initializes +/// it if it has not been already. +/// +/// This can be used to control the initialization point of a lazy static. +/// +/// Example: +/// +/// ```rust +/// #[macro_use] +/// extern crate lazy_static; +/// +/// lazy_static! { +/// static ref BUFFER: Vec<u8> = (0..65537).collect(); +/// } +/// +/// fn main() { +/// lazy_static::initialize(&BUFFER); +/// +/// // ... +/// work_with_initialized_data(&BUFFER); +/// } +/// # fn work_with_initialized_data(_: &[u8]) {} +/// ``` +pub fn initialize<T: LazyStatic>(lazy: &T) { + LazyStatic::initialize(lazy); +} diff --git a/lazy-static/tests/no_std.rs b/lazy-static/tests/no_std.rs new file mode 100644 index 0000000..f94a1aa --- /dev/null +++ b/lazy-static/tests/no_std.rs @@ -0,0 +1,20 @@ +#![cfg(feature="spin_no_std")] + +#![no_std] + +#[macro_use] +extern crate lazy_static; + +lazy_static! { + /// Documentation! + pub static ref NUMBER: u32 = times_two(3); +} + +fn times_two(n: u32) -> u32 { + n * 2 +} + +#[test] +fn test_basic() { + assert_eq!(*NUMBER, 6); +} diff --git a/lazy-static/tests/test.rs b/lazy-static/tests/test.rs new file mode 100644 index 0000000..654abc5 --- /dev/null +++ b/lazy-static/tests/test.rs @@ -0,0 +1,162 @@ +#[macro_use] +extern crate lazy_static; +use std::collections::HashMap; + +lazy_static! { + /// Documentation! + pub static ref NUMBER: u32 = times_two(3); + + static ref ARRAY_BOXES: [Box<u32>; 3] = [Box::new(1), Box::new(2), Box::new(3)]; + + /// More documentation! + #[allow(unused_variables)] + #[derive(Copy, Clone, Debug)] + pub static ref STRING: String = "hello".to_string(); + + static ref HASHMAP: HashMap<u32, &'static str> = { + let mut m = HashMap::new(); + m.insert(0, "abc"); + m.insert(1, "def"); + m.insert(2, "ghi"); + m + }; + + // This should not compile if the unsafe is removed. + static ref UNSAFE: u32 = unsafe { + std::mem::transmute::<i32, u32>(-1) + }; +} + +lazy_static! { + static ref S1: &'static str = "a"; + static ref S2: &'static str = "b"; +} +lazy_static! { + static ref S3: String = [*S1, *S2].join(""); +} + +#[test] +fn s3() { + assert_eq!(&*S3, "ab"); +} + +fn times_two(n: u32) -> u32 { + n * 2 +} + +#[test] +fn test_basic() { + assert_eq!(&**STRING, "hello"); + assert_eq!(*NUMBER, 6); + assert!(HASHMAP.get(&1).is_some()); + assert!(HASHMAP.get(&3).is_none()); + assert_eq!(&*ARRAY_BOXES, &[Box::new(1), Box::new(2), Box::new(3)]); + assert_eq!(*UNSAFE, std::u32::MAX); +} + +#[test] +fn test_repeat() { + assert_eq!(*NUMBER, 6); + assert_eq!(*NUMBER, 6); + assert_eq!(*NUMBER, 6); +} + +#[test] +fn test_meta() { + // this would not compile if STRING were not marked #[derive(Copy, Clone)] + let copy_of_string = STRING; + // just to make sure it was copied + assert!(&STRING as *const _ != ©_of_string as *const _); + + // this would not compile if STRING were not marked #[derive(Debug)] + assert_eq!(format!("{:?}", STRING), "STRING { __private_field: () }".to_string()); +} + +mod visibility { + lazy_static! { + pub static ref FOO: Box<u32> = Box::new(0); + static ref BAR: Box<u32> = Box::new(98); + } + + pub mod inner { + lazy_static! { + pub(in visibility) static ref BAZ: Box<u32> = Box::new(42); + pub(crate) static ref BAG: Box<u32> = Box::new(37); + } + } + + #[test] + fn sub_test() { + assert_eq!(**FOO, 0); + assert_eq!(**BAR, 98); + assert_eq!(**inner::BAZ, 42); + assert_eq!(**inner::BAG, 37); + } +} + +#[test] +fn test_visibility() { + assert_eq!(*visibility::FOO, Box::new(0)); + assert_eq!(*visibility::inner::BAG, Box::new(37)); +} + +// This should not cause a warning about a missing Copy implementation +lazy_static! { + pub static ref VAR: i32 = { 0 }; +} + +#[derive(Copy, Clone, Debug, PartialEq)] +struct X; +struct Once(X); +const ONCE_INIT: Once = Once(X); +static DATA: X = X; +static ONCE: X = X; +fn require_sync() -> X { X } +fn transmute() -> X { X } +fn __static_ref_initialize() -> X { X } +fn test(_: Vec<X>) -> X { X } + +// All these names should not be shadowed +lazy_static! { + static ref ITEM_NAME_TEST: X = { + test(vec![X, Once(X).0, ONCE_INIT.0, DATA, ONCE, + require_sync(), transmute(), + // Except this, which will sadly be shadowed by internals: + // __static_ref_initialize() + ]) + }; +} + +#[test] +fn item_name_shadowing() { + assert_eq!(*ITEM_NAME_TEST, X); +} + +use std::sync::atomic::AtomicBool; +use std::sync::atomic::ATOMIC_BOOL_INIT; +use std::sync::atomic::Ordering::SeqCst; + +static PRE_INIT_FLAG: AtomicBool = ATOMIC_BOOL_INIT; + +lazy_static! { + static ref PRE_INIT: () = { + PRE_INIT_FLAG.store(true, SeqCst); + () + }; +} + +#[test] +fn pre_init() { + assert_eq!(PRE_INIT_FLAG.load(SeqCst), false); + lazy_static::initialize(&PRE_INIT); + assert_eq!(PRE_INIT_FLAG.load(SeqCst), true); +} + +lazy_static! { + static ref LIFETIME_NAME: for<'a> fn(&'a u8) = { fn f(_: &u8) {} f }; +} + +#[test] +fn lifetime_name() { + let _ = LIFETIME_NAME; +} diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index dd40acd..7104c20 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased ---------- -- Bumped `nitrokey` dependency to `0.4.0-alpha.2` +- Bumped `nitrokey` dependency to `0.4.0-alpha.3` + - Bumped `nitrokey-sys` dependency to `3.5.0` + - Added `lazy_static` dependency in version `1.2.0` 0.3.0 diff --git a/nitrocli/Cargo.lock b/nitrocli/Cargo.lock index 168be50..cd90994 100644 --- a/nitrocli/Cargo.lock +++ b/nitrocli/Cargo.lock @@ -55,7 +55,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" @@ -77,32 +76,33 @@ dependencies = [ "argparse 0.2.2", "base32 0.4.0", "libc 0.2.66", - "nitrokey 0.4.0-alpha.2", - "nitrokey-test 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nitrokey 0.4.0-alpha.3", + "nitrokey-test 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "nitrokey-test-state 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nitrokey" -version = "0.4.0-alpha.2" +version = "0.4.0-alpha.3" dependencies = [ + "lazy_static 1.2.0", "libc 0.2.66", - "nitrokey-sys 3.4.3", + "nitrokey-sys 3.5.0", "rand_core 0.3.0", "rand_os 0.1.1", ] [[package]] name = "nitrokey-sys" -version = "3.4.3" +version = "3.5.0" dependencies = [ "cc 1.0.48", ] [[package]] name = "nitrokey-test" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)", @@ -190,7 +190,7 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0", ] [[package]] @@ -234,9 +234,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1dd4eaac298c32ce07eb6ed9242eda7d82955b9170b7d6db59b2e02cc63fcb8" -"checksum nitrokey-test 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f2195b25114e38da93d24169b074e7d1007238a0e33916e01b353099c09df379" +"checksum nitrokey-test 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e7e81b55db51769209e88a63cdbb4f2dc7ee9cd20ccaf32fbb940a3b0c50259" "checksum nitrokey-test-state 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a59b732ed6d5212424ed31ec9649f05652bcbc38f45f2292b27a6044e7098803" "checksum proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "38fddd23d98b2144d197c0eca5705632d4fe2667d14a6be5df8934f8d74f1978" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" diff --git a/nitrocli/Cargo.toml b/nitrocli/Cargo.toml index 1eb301e..23395c2 100644 --- a/nitrocli/Cargo.toml +++ b/nitrocli/Cargo.toml @@ -53,10 +53,10 @@ path = "../base32" version = "0.2" [dependencies.nitrokey] -version = "0.4.0-alpha.2" +version = "0.4.0-alpha.3" [dev-dependencies.nitrokey-test] -version = "0.2.1" +version = "0.3.1" [dev-dependencies.nitrokey-test-state] version = "0.1" @@ -71,5 +71,6 @@ cc = { path = "../cc" } libc = { path = "../libc" } nitrokey = { path = "../nitrokey" } nitrokey-sys = { path = "../nitrokey-sys" } +lazy_static = { path = "../lazy-static" } rand_core = { path = "../rand/rand_core" } rand_os = { path = "../rand/rand_os" } diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index ee410e5..f0e0f6e 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -60,16 +60,19 @@ fn set_log_level(ctx: &mut args::ExecCtx<'_>) { /// Connect to any Nitrokey device and do something with it. fn with_device<F>(ctx: &mut args::ExecCtx<'_>, op: F) -> Result<()> where - F: FnOnce(&mut args::ExecCtx<'_>, nitrokey::DeviceWrapper) -> Result<()>, + F: FnOnce(&mut args::ExecCtx<'_>, nitrokey::DeviceWrapper<'_>) -> Result<()>, { + let mut manager = nitrokey::take()?; set_log_level(ctx); let device = match ctx.model { - Some(model) => nitrokey::connect_model(model.into()).map_err(|_| { + Some(model) => manager.connect_model(model.into()).map_err(|_| { let error = format!("Nitrokey {} device not found", model.as_user_facing_str()); Error::Error(error) })?, - None => nitrokey::connect().map_err(|_| Error::from("Nitrokey device not found"))?, + None => manager + .connect() + .map_err(|_| Error::from("Nitrokey device not found"))?, }; op(ctx, device) @@ -78,8 +81,9 @@ where /// Connect to a Nitrokey Storage device and do something with it. fn with_storage_device<F>(ctx: &mut args::ExecCtx<'_>, op: F) -> Result<()> where - F: FnOnce(&mut args::ExecCtx<'_>, nitrokey::Storage) -> Result<()>, + F: FnOnce(&mut args::ExecCtx<'_>, nitrokey::Storage<'_>) -> Result<()>, { + let mut manager = nitrokey::take()?; set_log_level(ctx); if let Some(model) = ctx.model { @@ -90,8 +94,9 @@ where } } - let device = - nitrokey::Storage::connect().map_err(|_| Error::from("Nitrokey Storage device not found"))?; + let device = manager + .connect_storage() + .map_err(|_| Error::from("Nitrokey Storage device not found"))?; op(ctx, device) } @@ -99,7 +104,7 @@ where /// do something with it. fn with_password_safe<F>(ctx: &mut args::ExecCtx<'_>, mut op: F) -> Result<()> where - F: FnMut(&mut args::ExecCtx<'_>, nitrokey::PasswordSafe<'_>) -> Result<()>, + F: FnMut(&mut args::ExecCtx<'_>, nitrokey::PasswordSafe<'_, '_>) -> Result<()>, { with_device(ctx, |ctx, mut device| { let pin_entry = pinentry::PinEntry::from(pinentry::PinType::User, &device)?; @@ -123,7 +128,7 @@ where /// Authenticate the given device using the given PIN type and operation. /// /// If an error occurs, the error message `msg` is used. -fn authenticate<D, A, F>( +fn authenticate<'mgr, D, A, F>( ctx: &mut args::ExecCtx<'_>, device: D, pin_type: pinentry::PinType, @@ -131,7 +136,7 @@ fn authenticate<D, A, F>( op: F, ) -> Result<A> where - D: Device, + D: Device<'mgr>, F: FnMut(&mut args::ExecCtx<'_>, D, &str) -> result::Result<A, (D, nitrokey::Error)>, { let pin_entry = pinentry::PinEntry::from(pin_type, &device)?; @@ -140,9 +145,12 @@ where } /// Authenticate the given device with the user PIN. -fn authenticate_user<T>(ctx: &mut args::ExecCtx<'_>, device: T) -> Result<nitrokey::User<T>> +fn authenticate_user<'mgr, T>( + ctx: &mut args::ExecCtx<'_>, + device: T, +) -> Result<nitrokey::User<'mgr, T>> where - T: Device, + T: Device<'mgr>, { authenticate( ctx, @@ -154,9 +162,12 @@ where } /// Authenticate the given device with the admin PIN. -fn authenticate_admin<T>(ctx: &mut args::ExecCtx<'_>, device: T) -> Result<nitrokey::Admin<T>> +fn authenticate_admin<'mgr, T>( + ctx: &mut args::ExecCtx<'_>, + device: T, +) -> Result<nitrokey::Admin<'mgr, T>> where - T: Device, + T: Device<'mgr>, { authenticate( ctx, @@ -322,7 +333,7 @@ fn print_storage_status( fn print_status( ctx: &mut args::ExecCtx<'_>, model: &'static str, - device: &nitrokey::DeviceWrapper, + device: &nitrokey::DeviceWrapper<'_>, ) -> Result<()> { let serial_number = device .get_serial_number() @@ -700,7 +711,7 @@ pub fn otp_clear( fn print_otp_status( ctx: &mut args::ExecCtx<'_>, algorithm: args::OtpAlgorithm, - device: &nitrokey::DeviceWrapper, + device: &nitrokey::DeviceWrapper<'_>, all: bool, ) -> Result<()> { let mut slot: u8 = 0; @@ -843,7 +854,7 @@ fn print_pws_data( Ok(()) } -fn check_slot(pws: &nitrokey::PasswordSafe<'_>, slot: u8) -> Result<()> { +fn check_slot(pws: &nitrokey::PasswordSafe<'_, '_>, slot: u8) -> Result<()> { if slot >= nitrokey::SLOT_COUNT { return Err(nitrokey::Error::from(nitrokey::LibraryError::InvalidSlot).into()); } @@ -912,7 +923,7 @@ pub fn pws_clear(ctx: &mut args::ExecCtx<'_>, slot: u8) -> Result<()> { fn print_pws_slot( ctx: &mut args::ExecCtx<'_>, - pws: &nitrokey::PasswordSafe<'_>, + pws: &nitrokey::PasswordSafe<'_, '_>, slot: usize, programmed: bool, ) -> Result<()> { diff --git a/nitrocli/src/pinentry.rs b/nitrocli/src/pinentry.rs index 0733cd1..af28e2d 100644 --- a/nitrocli/src/pinentry.rs +++ b/nitrocli/src/pinentry.rs @@ -58,9 +58,9 @@ pub struct PinEntry { } impl PinEntry { - pub fn from<D>(pin_type: PinType, device: &D) -> crate::Result<Self> + pub fn from<'mgr, D>(pin_type: PinType, device: &D) -> crate::Result<Self> where - D: nitrokey::Device, + D: nitrokey::Device<'mgr>, { let model = device.get_model(); let serial = device.get_serial_number()?; @@ -131,9 +131,9 @@ pub struct PwdEntry { } impl PwdEntry { - pub fn from<D>(device: &D) -> crate::Result<Self> + pub fn from<'mgr, D>(device: &D) -> crate::Result<Self> where - D: nitrokey::Device, + D: nitrokey::Device<'mgr>, { let model = device.get_model(); let serial = device.get_serial_number()?; diff --git a/nitrocli/src/tests/config.rs b/nitrocli/src/tests/config.rs index 8983cb8..ea3a0e8 100644 --- a/nitrocli/src/tests/config.rs +++ b/nitrocli/src/tests/config.rs @@ -20,7 +20,7 @@ use super::*; #[test_device] -fn get(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn get(model: nitrokey::Model) -> crate::Result<()> { let re = regex::Regex::new( r#"^Config: numlock binding: (not set|\d+) @@ -31,14 +31,14 @@ $"#, ) .unwrap(); - let out = Nitrocli::with_dev(device).handle(&["config", "get"])?; + let out = Nitrocli::with_model(model).handle(&["config", "get"])?; assert!(re.is_match(&out), out); Ok(()) } #[test_device] -fn set_wrong_usage(device: nitrokey::DeviceWrapper) { - let res = Nitrocli::with_dev(device).handle(&["config", "set", "--numlock", "2", "-N"]); +fn set_wrong_usage(model: nitrokey::Model) { + let res = Nitrocli::with_model(model).handle(&["config", "set", "--numlock", "2", "-N"]); assert_eq!( res.unwrap_str_err(), "--numlock and --no-numlock are mutually exclusive" @@ -46,8 +46,8 @@ fn set_wrong_usage(device: nitrokey::DeviceWrapper) { } #[test_device] -fn set_get(device: nitrokey::DeviceWrapper) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +fn set_get(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["config", "set", "-s", "1", "-c", "0", "-N"])?; let re = regex::Regex::new( diff --git a/nitrocli/src/tests/encrypted.rs b/nitrocli/src/tests/encrypted.rs index 8aef864..75b84c3 100644 --- a/nitrocli/src/tests/encrypted.rs +++ b/nitrocli/src/tests/encrypted.rs @@ -19,8 +19,8 @@ use super::*; -#[test_device] -fn status_open_close(device: nitrokey::Storage) -> crate::Result<()> { +#[test_device(storage)] +fn status_open_close(model: nitrokey::Model) -> crate::Result<()> { fn make_re(open: Option<bool>) -> regex::Regex { let encrypted = match open { Some(open) => { @@ -44,7 +44,7 @@ $"#, regex::Regex::new(&re).unwrap() } - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); let out = ncli.handle(&["status"])?; assert!(make_re(None).is_match(&out), out); @@ -59,32 +59,37 @@ $"#, Ok(()) } -#[test_device] -fn encrypted_open_on_pro(device: nitrokey::Pro) { - let res = Nitrocli::with_dev(device).handle(&["encrypted", "open"]); +#[test_device(pro)] +fn encrypted_open_on_pro(model: nitrokey::Model) { + let res = Nitrocli::with_model(model).handle(&["encrypted", "open"]); assert_eq!( res.unwrap_str_err(), "This command is only available on the Nitrokey Storage", ); } -#[test_device] -fn encrypted_open_close(device: nitrokey::Storage) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +#[test_device(storage)] +fn encrypted_open_close(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let out = ncli.handle(&["encrypted", "open"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; - assert!(device.get_status()?.encrypted_volume.active); - assert!(!device.get_status()?.hidden_volume.active); - drop(device); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; + assert!(device.get_status()?.encrypted_volume.active); + assert!(!device.get_status()?.hidden_volume.active); + } let out = ncli.handle(&["encrypted", "close"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; - assert!(!device.get_status()?.encrypted_volume.active); - assert!(!device.get_status()?.hidden_volume.active); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; + assert!(!device.get_status()?.encrypted_volume.active); + assert!(!device.get_status()?.hidden_volume.active); + } Ok(()) } diff --git a/nitrocli/src/tests/hidden.rs b/nitrocli/src/tests/hidden.rs index 483a801..28a5d23 100644 --- a/nitrocli/src/tests/hidden.rs +++ b/nitrocli/src/tests/hidden.rs @@ -19,26 +19,31 @@ use super::*; -#[test_device] -fn hidden_create_open_close(device: nitrokey::Storage) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +#[test_device(storage)] +fn hidden_create_open_close(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let out = ncli.handle(&["hidden", "create", "0", "50", "100"])?; assert!(out.is_empty()); let out = ncli.handle(&["hidden", "open"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; - assert!(!device.get_status()?.encrypted_volume.active); - assert!(device.get_status()?.hidden_volume.active); - drop(device); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; + assert!(!device.get_status()?.encrypted_volume.active); + assert!(device.get_status()?.hidden_volume.active); + } let out = ncli.handle(&["hidden", "close"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; - assert!(!device.get_status()?.encrypted_volume.active); - assert!(!device.get_status()?.hidden_volume.active); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; + assert!(!device.get_status()?.encrypted_volume.active); + assert!(!device.get_status()?.hidden_volume.active); + } Ok(()) } diff --git a/nitrocli/src/tests/lock.rs b/nitrocli/src/tests/lock.rs index d23d2ae..5140152 100644 --- a/nitrocli/src/tests/lock.rs +++ b/nitrocli/src/tests/lock.rs @@ -19,24 +19,25 @@ use super::*; -#[test_device] -fn lock_pro(device: nitrokey::Pro) -> crate::Result<()> { +#[test_device(pro)] +fn lock_pro(model: nitrokey::Model) -> crate::Result<()> { // We can't really test much more here than just success of the command. - let out = Nitrocli::with_dev(device).handle(&["lock"])?; + let out = Nitrocli::with_model(model).handle(&["lock"])?; assert!(out.is_empty()); Ok(()) } -#[test_device] -fn lock_storage(device: nitrokey::Storage) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +#[test_device(storage)] +fn lock_storage(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["encrypted", "open"])?; let out = ncli.handle(&["lock"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; assert!(!device.get_status()?.encrypted_volume.active); Ok(()) diff --git a/nitrocli/src/tests/mod.rs b/nitrocli/src/tests/mod.rs index e8af624..1e2fe26 100644 --- a/nitrocli/src/tests/mod.rs +++ b/nitrocli/src/tests/mod.rs @@ -97,21 +97,18 @@ impl Nitrocli { } } - pub fn with_dev<D>(device: D) -> Self + pub fn with_model<M>(model: M) -> Self where - D: nitrokey::Device, + M: Into<nitrokey::Model>, { - let result = Self { - model: Some(device.get_model()), + Self { + model: Some(model.into()), admin_pin: Some(nitrokey::DEFAULT_ADMIN_PIN.into()), user_pin: Some(nitrokey::DEFAULT_USER_PIN.into()), new_admin_pin: None, new_user_pin: None, password: Some("1234567".into()), - }; - - drop(device); - result + } } pub fn admin_pin(&mut self, pin: impl Into<ffi::OsString>) { diff --git a/nitrocli/src/tests/otp.rs b/nitrocli/src/tests/otp.rs index 2893e80..0ccecf9 100644 --- a/nitrocli/src/tests/otp.rs +++ b/nitrocli/src/tests/otp.rs @@ -22,8 +22,8 @@ use super::*; use crate::args; #[test_device] -fn set_invalid_slot_raw(device: nitrokey::DeviceWrapper) { - let (rc, out, err) = Nitrocli::with_dev(device).run(&["otp", "set", "100", "name", "1234"]); +fn set_invalid_slot_raw(model: nitrokey::Model) { + let (rc, out, err) = Nitrocli::with_model(model).run(&["otp", "set", "100", "name", "1234"]); assert_ne!(rc, 0); assert_eq!(out, b""); @@ -31,8 +31,8 @@ fn set_invalid_slot_raw(device: nitrokey::DeviceWrapper) { } #[test_device] -fn set_invalid_slot(device: nitrokey::DeviceWrapper) { - let res = Nitrocli::with_dev(device).handle(&["otp", "set", "100", "name", "1234"]); +fn set_invalid_slot(model: nitrokey::Model) { + let res = Nitrocli::with_model(model).handle(&["otp", "set", "100", "name", "1234"]); assert_eq!( res.unwrap_lib_err(), @@ -44,14 +44,14 @@ fn set_invalid_slot(device: nitrokey::DeviceWrapper) { } #[test_device] -fn status(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn status(model: nitrokey::Model) -> crate::Result<()> { let re = regex::Regex::new( r#"^alg\tslot\tname ((totp|hotp)\t\d+\t.+\n)+$"#, ) .unwrap(); - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); // Make sure that we have at least something to display by ensuring // that there is one slot programmed. let _ = ncli.handle(&["otp", "set", "0", "the-name", "123456"])?; @@ -62,14 +62,14 @@ fn status(device: nitrokey::DeviceWrapper) -> crate::Result<()> { } #[test_device] -fn set_get_hotp(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn set_get_hotp(model: nitrokey::Model) -> crate::Result<()> { // Secret and expected HOTP values as per RFC 4226: Appendix D -- HOTP // Algorithm: Test Values. const SECRET: &str = "12345678901234567890"; const OTP1: &str = concat!(755224, "\n"); const OTP2: &str = concat!(287082, "\n"); - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&[ "otp", "set", "-a", "hotp", "-f", "ascii", "1", "name", &SECRET, ])?; @@ -83,14 +83,14 @@ fn set_get_hotp(device: nitrokey::DeviceWrapper) -> crate::Result<()> { } #[test_device] -fn set_get_totp(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn set_get_totp(model: nitrokey::Model) -> crate::Result<()> { // Secret and expected TOTP values as per RFC 6238: Appendix B -- // Test Vectors. const SECRET: &str = "12345678901234567890"; const TIME: &str = stringify!(1111111111); const OTP: &str = concat!(14050471, "\n"); - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["otp", "set", "-d", "8", "-f", "ascii", "2", "name", &SECRET])?; let out = ncli.handle(&["otp", "get", "-t", TIME, "2"])?; @@ -99,22 +99,22 @@ fn set_get_totp(device: nitrokey::DeviceWrapper) -> crate::Result<()> { } #[test_device] -fn set_totp_uneven_chars(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn set_totp_uneven_chars(model: nitrokey::Model) -> crate::Result<()> { let secrets = [ (args::OtpSecretFormat::Hex, "123"), (args::OtpSecretFormat::Base32, "FBILDWWGA2"), ]; - let mut ncli = Nitrocli::with_dev(device); for (format, secret) in &secrets { + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["otp", "set", "-f", format.as_ref(), "3", "foobar", &secret])?; } Ok(()) } #[test_device] -fn clear(device: nitrokey::DeviceWrapper) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +fn clear(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["otp", "set", "3", "hotp-test", "abcdef"])?; let _ = ncli.handle(&["otp", "clear", "3"])?; let res = ncli.handle(&["otp", "get", "3"]); diff --git a/nitrocli/src/tests/pin.rs b/nitrocli/src/tests/pin.rs index e4cd316..958a36d 100644 --- a/nitrocli/src/tests/pin.rs +++ b/nitrocli/src/tests/pin.rs @@ -23,40 +23,48 @@ use nitrokey::Device; use super::*; #[test_device] -fn unblock(device: nitrokey::DeviceWrapper) -> crate::Result<()> { - let (device, err) = device.authenticate_user("wrong-pin").unwrap_err(); - match err { - nitrokey::Error::CommandError(err) if err == nitrokey::CommandError::WrongPassword => (), - _ => panic!("Unexpected error variant found: {:?}", err), +fn unblock(model: nitrokey::Model) -> crate::Result<()> { + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_model(model)?; + let (device, err) = device.authenticate_user("wrong-pin").unwrap_err(); + match err { + nitrokey::Error::CommandError(err) if err == nitrokey::CommandError::WrongPassword => (), + _ => panic!("Unexpected error variant found: {:?}", err), + } + assert!(device.get_user_retry_count()? < 3); } - assert!(device.get_user_retry_count()? < 3); - let model = device.get_model(); - let _ = Nitrocli::with_dev(device).handle(&["pin", "unblock"])?; - let device = nitrokey::connect_model(model)?; - assert_eq!(device.get_user_retry_count()?, 3); + let _ = Nitrocli::with_model(model).handle(&["pin", "unblock"])?; + + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_model(model)?; + assert_eq!(device.get_user_retry_count()?, 3); + } Ok(()) } #[test_device] -fn set_user(device: nitrokey::DeviceWrapper) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); - +fn set_user(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); // Set a new user PIN. ncli.new_user_pin("new-pin"); let out = ncli.handle(&["pin", "set", "user"])?; assert!(out.is_empty()); - let device = nitrokey::connect_model(ncli.model().unwrap())?; - let (device, err) = device - .authenticate_user(nitrokey::DEFAULT_USER_PIN) - .unwrap_err(); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_model(model)?; + let (_, err) = device + .authenticate_user(nitrokey::DEFAULT_USER_PIN) + .unwrap_err(); - match err { - nitrokey::Error::CommandError(err) if err == nitrokey::CommandError::WrongPassword => (), - _ => panic!("Unexpected error variant found: {:?}", err), + match err { + nitrokey::Error::CommandError(err) if err == nitrokey::CommandError::WrongPassword => (), + _ => panic!("Unexpected error variant found: {:?}", err), + } } - drop(device); // Revert to the default user PIN. ncli.user_pin("new-pin"); @@ -65,9 +73,12 @@ fn set_user(device: nitrokey::DeviceWrapper) -> crate::Result<()> { let out = ncli.handle(&["pin", "set", "user"])?; assert!(out.is_empty()); - let device = nitrokey::connect_model(ncli.model().unwrap())?; - let _ = device - .authenticate_user(nitrokey::DEFAULT_USER_PIN) - .unwrap(); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_model(ncli.model().unwrap())?; + let _ = device + .authenticate_user(nitrokey::DEFAULT_USER_PIN) + .unwrap(); + } Ok(()) } diff --git a/nitrocli/src/tests/pws.rs b/nitrocli/src/tests/pws.rs index 9468dcf..651b2d5 100644 --- a/nitrocli/src/tests/pws.rs +++ b/nitrocli/src/tests/pws.rs @@ -20,8 +20,8 @@ use super::*; #[test_device] -fn set_invalid_slot(device: nitrokey::DeviceWrapper) { - let res = Nitrocli::with_dev(device).handle(&["pws", "set", "100", "name", "login", "1234"]); +fn set_invalid_slot(model: nitrokey::Model) { + let res = Nitrocli::with_model(model).handle(&["pws", "set", "100", "name", "login", "1234"]); assert_eq!( res.unwrap_lib_err(), @@ -33,14 +33,14 @@ fn set_invalid_slot(device: nitrokey::DeviceWrapper) { } #[test_device] -fn status(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn status(model: nitrokey::Model) -> crate::Result<()> { let re = regex::Regex::new( r#"^slot\tname (\d+\t.+\n)+$"#, ) .unwrap(); - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); // Make sure that we have at least something to display by ensuring // that there are there is one slot programmed. let _ = ncli.handle(&["pws", "set", "0", "the-name", "the-login", "123456"])?; @@ -51,12 +51,12 @@ fn status(device: nitrokey::DeviceWrapper) -> crate::Result<()> { } #[test_device] -fn set_get(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn set_get(model: nitrokey::Model) -> crate::Result<()> { const NAME: &str = "dropbox"; const LOGIN: &str = "d-e-s-o"; const PASSWORD: &str = "my-secret-password"; - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["pws", "set", "1", &NAME, &LOGIN, &PASSWORD])?; let out = ncli.handle(&["pws", "get", "1", "--quiet", "--name"])?; @@ -83,12 +83,12 @@ fn set_get(device: nitrokey::DeviceWrapper) -> crate::Result<()> { } #[test_device] -fn set_reset_get(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn set_reset_get(model: nitrokey::Model) -> crate::Result<()> { const NAME: &str = "some/svc"; const LOGIN: &str = "a\\user"; const PASSWORD: &str = "!@&-)*(&+%^@"; - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["pws", "set", "2", &NAME, &LOGIN, &PASSWORD])?; let out = ncli.handle(&["reset"])?; @@ -106,8 +106,8 @@ fn set_reset_get(device: nitrokey::DeviceWrapper) -> crate::Result<()> { } #[test_device] -fn clear(device: nitrokey::DeviceWrapper) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +fn clear(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let _ = ncli.handle(&["pws", "set", "10", "clear-test", "some-login", "abcdef"])?; let _ = ncli.handle(&["pws", "clear", "10"])?; let res = ncli.handle(&["pws", "get", "10"]); diff --git a/nitrocli/src/tests/reset.rs b/nitrocli/src/tests/reset.rs index f9452ec..e197970 100644 --- a/nitrocli/src/tests/reset.rs +++ b/nitrocli/src/tests/reset.rs @@ -23,32 +23,38 @@ use nitrokey::GetPasswordSafe; use super::*; #[test_device] -fn reset(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn reset(model: nitrokey::Model) -> crate::Result<()> { let new_admin_pin = "87654321"; - let mut ncli = Nitrocli::with_dev(device); + let mut ncli = Nitrocli::with_model(model); // Change the admin PIN. ncli.new_admin_pin(new_admin_pin); let _ = ncli.handle(&["pin", "set", "admin"])?; - // Check that the admin PIN has been changed. - let device = nitrokey::connect_model(ncli.model().unwrap())?; - let _ = device.authenticate_admin(new_admin_pin).unwrap(); + { + let mut manager = nitrokey::force_take()?; + // Check that the admin PIN has been changed. + let device = manager.connect_model(ncli.model().unwrap())?; + let _ = device.authenticate_admin(new_admin_pin).unwrap(); + } // Perform factory reset ncli.admin_pin(new_admin_pin); let out = ncli.handle(&["reset"])?; assert!(out.is_empty()); - // Check that the admin PIN has been reset. - let device = nitrokey::connect_model(ncli.model().unwrap())?; - let mut device = device - .authenticate_admin(nitrokey::DEFAULT_ADMIN_PIN) - .unwrap(); - - // Check that the password store works, i.e., the AES key has been - // built. - let _ = device.get_password_safe(nitrokey::DEFAULT_USER_PIN)?; + { + let mut manager = nitrokey::force_take()?; + // Check that the admin PIN has been reset. + let device = manager.connect_model(ncli.model().unwrap())?; + let mut device = device + .authenticate_admin(nitrokey::DEFAULT_ADMIN_PIN) + .unwrap(); + + // Check that the password store works, i.e., the AES key has been + // built. + let _ = device.get_password_safe(nitrokey::DEFAULT_USER_PIN)?; + } Ok(()) } diff --git a/nitrocli/src/tests/status.rs b/nitrocli/src/tests/status.rs index 7aac5ad..c9f4976 100644 --- a/nitrocli/src/tests/status.rs +++ b/nitrocli/src/tests/status.rs @@ -36,31 +36,31 @@ fn not_found() { assert_eq!(res.unwrap_str_err(), "Nitrokey device not found"); } -#[test_device] -fn output_pro(device: nitrokey::Pro) -> crate::Result<()> { +#[test_device(pro)] +fn output_pro(model: nitrokey::Model) -> crate::Result<()> { let re = regex::Regex::new( r#"^Status: model: Pro serial number: 0x[[:xdigit:]]{8} - firmware version: \d+\.\d+ + firmware version: v\d+\.\d+ user retry count: [0-3] admin retry count: [0-3] $"#, ) .unwrap(); - let out = Nitrocli::with_dev(device).handle(&["status"])?; + let out = Nitrocli::with_model(model).handle(&["status"])?; assert!(re.is_match(&out), out); Ok(()) } -#[test_device] -fn output_storage(device: nitrokey::Storage) -> crate::Result<()> { +#[test_device(storage)] +fn output_storage(model: nitrokey::Model) -> crate::Result<()> { let re = regex::Regex::new( r#"^Status: model: Storage serial number: 0x[[:xdigit:]]{8} - firmware version: \d+\.\d+ + firmware version: v\d+\.\d+ user retry count: [0-3] admin retry count: [0-3] Storage: @@ -75,7 +75,7 @@ $"#, ) .unwrap(); - let out = Nitrocli::with_dev(device).handle(&["status"])?; + let out = Nitrocli::with_model(model).handle(&["status"])?; assert!(re.is_match(&out), out); Ok(()) } diff --git a/nitrocli/src/tests/unencrypted.rs b/nitrocli/src/tests/unencrypted.rs index c976f50..547dcaf 100644 --- a/nitrocli/src/tests/unencrypted.rs +++ b/nitrocli/src/tests/unencrypted.rs @@ -19,23 +19,28 @@ use super::*; -#[test_device] -fn unencrypted_set_read_write(device: nitrokey::Storage) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); +#[test_device(storage)] +fn unencrypted_set_read_write(model: nitrokey::Model) -> crate::Result<()> { + let mut ncli = Nitrocli::with_model(model); let out = ncli.handle(&["unencrypted", "set", "read-write"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; - assert!(device.get_status()?.unencrypted_volume.active); - assert!(!device.get_status()?.unencrypted_volume.read_only); - drop(device); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; + assert!(device.get_status()?.unencrypted_volume.active); + assert!(!device.get_status()?.unencrypted_volume.read_only); + } let out = ncli.handle(&["unencrypted", "set", "read-only"])?; assert!(out.is_empty()); - let device = nitrokey::Storage::connect()?; - assert!(device.get_status()?.unencrypted_volume.active); - assert!(device.get_status()?.unencrypted_volume.read_only); + { + let mut manager = nitrokey::force_take()?; + let device = manager.connect_storage()?; + assert!(device.get_status()?.unencrypted_volume.active); + assert!(device.get_status()?.unencrypted_volume.read_only); + } Ok(()) } diff --git a/nitrokey-sys/CHANGELOG.md b/nitrokey-sys/CHANGELOG.md index a11d168..a440ca1 100644 --- a/nitrokey-sys/CHANGELOG.md +++ b/nitrokey-sys/CHANGELOG.md @@ -1,3 +1,29 @@ +# v3.5.0 (2019-07-04) +- Mark deprecated functions using the `deprecated` attribute. +- Update to libnitrokey 3.5, causing all following changes. +- New constant `NK_PWS_SLOT_COUNT`. +- New structures: + - `NK_device_info` + - `NK_status` + - `NK_SD_usage_data` + - `ReadSlot_t` +- New functions: + - `NK_get_SD_usage_data` + - `NK_get_status` + - `NK_get_status_as_string` + - `NK_list_devices` + - `NK_free_device_info` + - `NK_connect_with_path` + - `NK_enable_firmware_update_pro` + - `NK_change_firmware_password_pro` + - `NK_read_HOTP_slot` +- Deprecated functions: + - `NK_status` +- Changed the return type for `NK_get_major_firmware_version` and + `NK_get_minor_firmware_version` to `u8`. +- Changed `NK_get_progress_bar_value` to return -2 instead of 0 if an error + occurs. + # v3.4.3 (2019-10-12) - Link directly against `libnitrokey` if the `USE_SYSTEM_LIBNITROKEY` environment variable is set. diff --git a/nitrokey-sys/Cargo.toml b/nitrokey-sys/Cargo.toml index f9d304b..068670b 100644 --- a/nitrokey-sys/Cargo.toml +++ b/nitrokey-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nitrokey-sys" -version = "3.4.3" +version = "3.5.0" authors = ["Robin Krahl <robin.krahl@ireas.org>"] edition = "2018" homepage = "https://code.ireas.org/nitrokey-rs/" diff --git a/nitrokey-sys/build.rs b/nitrokey-sys/build.rs index defce72..a9d0778 100644 --- a/nitrokey-sys/build.rs +++ b/nitrokey-sys/build.rs @@ -25,8 +25,8 @@ impl string::ToString for Version { const LIBNITROKEY_VERSION: Version = Version { major: 3, - minor: 4, - patch: Some(1), + minor: 5, + patch: None, }; fn prepare_version_source( diff --git a/nitrokey-sys/libnitrokey-v3.4.1/DeviceCommunicationExceptions.cpp b/nitrokey-sys/libnitrokey-v3.5/DeviceCommunicationExceptions.cpp index 4d62aad..4d62aad 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/DeviceCommunicationExceptions.cpp +++ b/nitrokey-sys/libnitrokey-v3.5/DeviceCommunicationExceptions.cpp diff --git a/nitrokey-sys/libnitrokey-v3.4.1/LICENSE b/nitrokey-sys/libnitrokey-v3.5/LICENSE index 341c30b..341c30b 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/LICENSE +++ b/nitrokey-sys/libnitrokey-v3.5/LICENSE diff --git a/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.cc b/nitrokey-sys/libnitrokey-v3.5/NK_C_API.cc index 7d0a10e..1d3fa3a 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.cc +++ b/nitrokey-sys/libnitrokey-v3.5/NK_C_API.cc @@ -27,7 +27,8 @@ #include "libnitrokey/LibraryException.h" #include "libnitrokey/cxx_semantics.h" #include "libnitrokey/stick20_commands.h" -#include "version.h" +#include "libnitrokey/device_proto.h" +#include "libnitrokey/version.h" #ifdef _MSC_VER #ifdef _WIN32 @@ -44,6 +45,7 @@ char * strndup(const char* str, size_t maxlen) { using namespace nitrokey; +const uint8_t NK_PWS_SLOT_COUNT = PWS_SLOT_COUNT; static uint8_t NK_last_command_status = 0; static const int max_string_field_length = 100; @@ -250,6 +252,10 @@ extern "C" { NK_C_API char * NK_status() { + return NK_get_status_as_string(); + } + + NK_C_API char * NK_get_status_as_string() { auto m = NitrokeyManager::instance(); return get_with_string_result([&]() { string && s = m->get_status_as_string(); @@ -259,6 +265,30 @@ extern "C" { }); } + NK_C_API int NK_get_status(struct NK_status* out) { + if (out == nullptr) { + return -1; + } + auto m = NitrokeyManager::instance(); + auto result = get_with_status([&]() { + return m->get_status(); + }, proto::stick10::GetStatus::ResponsePayload()); + auto error_code = std::get<0>(result); + if (error_code != 0) { + return error_code; + } + + auto status = std::get<1>(result); + out->firmware_version_major = status.firmware_version_st.major; + out->firmware_version_minor = status.firmware_version_st.minor; + out->serial_number_smart_card = status.card_serial_u32; + out->config_numlock = status.numlock; + out->config_capslock = status.capslock; + out->config_scrolllock = status.scrolllock; + out->otp_user_password = status.enable_user_password != 0; + return 0; + } + NK_C_API char * NK_device_serial_number() { auto m = NitrokeyManager::instance(); return get_with_string_result([&]() { @@ -687,6 +717,23 @@ extern "C" { return 0; } + NK_C_API int NK_get_SD_usage_data(struct NK_SD_usage_data* out) { + if (out == nullptr) + return -1; + auto m = NitrokeyManager::instance(); + auto result = get_with_status([&]() { + return m->get_SD_usage_data(); + }, std::make_pair<uint8_t, uint8_t>(0, 0)); + auto error_code = std::get<0>(result); + if (error_code != 0) + return error_code; + + auto data = std::get<1>(result); + out->write_level_min = std::get<0>(data); + out->write_level_max = std::get<1>(data); + + return 0; + } NK_C_API char* NK_get_SD_usage_data_as_string() { auto m = NitrokeyManager::instance(); @@ -697,19 +744,19 @@ NK_C_API char* NK_get_SD_usage_data_as_string() { NK_C_API int NK_get_progress_bar_value() { auto m = NitrokeyManager::instance(); - return get_with_result([&]() { + return std::get<1>(get_with_status([&]() { return m->get_progress_bar_value(); - }); + }, -2)); } - NK_C_API int NK_get_major_firmware_version() { + NK_C_API uint8_t NK_get_major_firmware_version() { auto m = NitrokeyManager::instance(); return get_with_result([&]() { return m->get_major_firmware_version(); }); } - NK_C_API int NK_get_minor_firmware_version() { + NK_C_API uint8_t NK_get_minor_firmware_version() { auto m = NitrokeyManager::instance(); return get_with_result([&]() { return m->get_minor_firmware_version(); @@ -736,6 +783,66 @@ NK_C_API char* NK_get_SD_usage_data_as_string() { }); } + bool copy_device_info(const DeviceInfo& source, NK_device_info* target) { + switch (source.m_deviceModel) { + case DeviceModel::PRO: + target->model = NK_PRO; + break; + case DeviceModel::STORAGE: + target->model = NK_STORAGE; + break; + default: + return false; + } + + target->path = strndup(source.m_path.c_str(), MAXIMUM_STR_REPLY_LENGTH); + target->serial_number = strndup(source.m_serialNumber.c_str(), MAXIMUM_STR_REPLY_LENGTH); + target->next = nullptr; + + return target->path && target->serial_number; + } + + NK_C_API struct NK_device_info* NK_list_devices() { + auto nm = NitrokeyManager::instance(); + return get_with_result([&]() -> NK_device_info* { + auto v = nm->list_devices(); + if (v.empty()) + return nullptr; + + auto result = new NK_device_info(); + auto ptr = result; + auto first = v.begin(); + if (!copy_device_info(*first, ptr)) { + NK_free_device_info(result); + return nullptr; + } + v.erase(first); + + for (auto& info : v) { + ptr->next = new NK_device_info(); + ptr = ptr->next; + + if (!copy_device_info(info, ptr)) { + NK_free_device_info(result); + return nullptr; + } + } + return result; + }); + } + + NK_C_API void NK_free_device_info(struct NK_device_info* device_info) { + if (!device_info) + return; + + if (device_info->next) + NK_free_device_info(device_info->next); + + free(device_info->path); + free(device_info->serial_number); + delete device_info; + } + NK_C_API int NK_connect_with_ID(const char* id) { auto m = NitrokeyManager::instance(); return get_with_result([&]() { @@ -743,6 +850,14 @@ NK_C_API char* NK_get_SD_usage_data_as_string() { }); } + NK_C_API int NK_connect_with_path(const char* path) { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->connect_with_path(path) ? 1 : 0; + }); + } + + NK_C_API int NK_wink() { auto m = NitrokeyManager::instance(); return get_without_result([&]() { @@ -750,6 +865,46 @@ NK_C_API char* NK_get_SD_usage_data_as_string() { }); } + NK_C_API int NK_enable_firmware_update_pro(const char* update_password){ + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->enable_firmware_update_pro(update_password); + }); +} + + NK_C_API int NK_change_firmware_password_pro(const char *current_firmware_password, const char *new_firmware_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->change_firmware_update_password_pro(current_firmware_password, + new_firmware_password); + }); + } + + + NK_C_API int NK_read_HOTP_slot(const uint8_t slot_num, struct ReadSlot_t* out){ + if (out == nullptr) + return -1; + auto m = NitrokeyManager::instance(); + auto result = get_with_status([&]() { + return m->get_HOTP_slot_data(slot_num); + }, stick10::ReadSlot::ResponsePayload() ); + auto error_code = std::get<0>(result); + if (error_code != 0) { + return error_code; + } +#define a(x) out->x = read_slot.x + stick10::ReadSlot::ResponsePayload read_slot = std::get<1>(result); + a(_slot_config); + a(slot_counter); +#undef a +#define m(x) memmove(out->x, read_slot.x, sizeof(read_slot.x)) + m(slot_name); + m(slot_token_id); +#undef m + return 0; +} + + #ifdef __cplusplus } #endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.h b/nitrokey-sys/libnitrokey-v3.5/NK_C_API.h index b1bdf1e..d5c54a3 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.h +++ b/nitrokey-sys/libnitrokey-v3.5/NK_C_API.h @@ -33,10 +33,67 @@ #define NK_C_API #endif +/** + * \file + * + * C API for libnitrokey + * + * \mainpage + * + * **libnitrokey** provides access to Nitrokey Pro and Nitrokey Storage devices. + * This documentation describes libnitrokey’s C API. For a list of the + * available functions, see the NK_C_API.h file. + * + * \section getting_started Example + * + * \code{.c} + * #include <stdio.h> + * #include <stdlib.h> + * #include <libnitrokey/NK_C_API.h> + * + * int main(void) + * { + * if (NK_login_auto() != 1) { + * fprintf(stderr, "No Nitrokey found.\n"); + * return 1; + * } + * + * NK_device_model model = NK_get_device_model(); + * printf("Connected to "); + * switch (model) { + * case NK_PRO: + * printf("a Nitrokey Pro"); + * break; + * case NK_STORAGE: + * printf("a Nitrokey Storage"); + * break; + * default: + * printf("an unsupported Nitrokey"); + * break; + * } + * + * char* serial_number = NK_device_serial_number(); + * if (serial_number) + * printf(" with serial number %s\n", serial_number); + * else + * printf(" -- could not query serial number!\n"); + * free(serial_number); + * + * NK_logout(); + * return 0; + * } + * \endcode + */ + #ifdef __cplusplus extern "C" { #endif + /** + * The number of slots in the password safe. + */ + extern const uint8_t NK_PWS_SLOT_COUNT; + static const int MAXIMUM_STR_REPLY_LENGTH = 8192; /** @@ -57,6 +114,70 @@ extern "C" { NK_STORAGE = 2 }; + /** + * The connection info for a Nitrokey device as a linked list. + */ + struct NK_device_info { + /** + * The model of the Nitrokey device. + */ + enum NK_device_model model; + /** + * The USB device path for NK_connect_with_path. + */ + char* path; + /** + * The serial number. + */ + char* serial_number; + /** + * The pointer to the next element of the linked list or null + * if this is the last element in the list. + */ + struct NK_device_info* next; + }; + + /** + * Stores the common device status for all Nitrokey devices. + */ + struct NK_status { + /** + * The major firmware version, e. g. 0 in v0.40. + */ + uint8_t firmware_version_major; + /** + * The minor firmware version, e. g. 40 in v0.40. + */ + uint8_t firmware_version_minor; + /** + * The serial number of the smart card. + */ + uint32_t serial_number_smart_card; + /** + * The HOTP slot to generate a password from if the numlock + * key is pressed twice (slot 0-1, or any other value to + * disable the function). + */ + uint8_t config_numlock; + /** + * The HOTP slot to generate a password from if the capslock + * key is pressed twice (slot 0-1, or any other value to + * disable the function). + */ + uint8_t config_capslock; + /** + * The HOTP slot to generate a password from if the scrolllock + * key is pressed twice (slot 0-1, or any other value to + * disable the function). + */ + uint8_t config_scrolllock; + /** + * Indicates whether the user password is required to generate + * an OTP value. + */ + bool otp_user_password; + }; + /** * Stores the status of a Storage device. */ @@ -128,6 +249,23 @@ extern "C" { bool stick_initialized; }; + /** + * Data about the usage of the SD card. + */ + struct NK_SD_usage_data { + /** + * The minimum write level, as a percentage of the total card + * size. + */ + uint8_t write_level_min; + /** + * The maximum write level, as a percentage of the total card + * size. + */ + uint8_t write_level_max; + }; + + struct NK_storage_ProductionTest{ uint8_t FirmwareVersion_au8[2]; uint8_t FirmwareVersionInternal_u8; @@ -215,10 +353,31 @@ extern "C" { NK_C_API enum NK_device_model NK_get_device_model(); /** + * Return the debug status string. Debug purposes. This function is + * deprecated in favor of NK_get_status_as_string. + * @return string representation of the status or an empty string + * if the command failed + */ + DEPRECATED + NK_C_API char * NK_status(); + + /** * Return the debug status string. Debug purposes. + * @return string representation of the status or an empty string + * if the command failed + */ + NK_C_API char * NK_get_status_as_string(); + + /** + * Get the stick status common to all Nitrokey devices and return the + * command processing error code. If the code is zero, i. e. the + * command was successful, the storage status is written to the output + * pointer's target. The output pointer must not be null. + * + * @param out the output pointer for the status * @return command processing error code */ - NK_C_API char * NK_status(); + NK_C_API int NK_get_status(struct NK_status* out); /** * Return the device's serial number string in hex. @@ -528,13 +687,13 @@ extern "C" { * Get device's major firmware version * @return major part of the version number (e.g. 0 from 0.48, 0 from 0.7 etc.) */ - NK_C_API int NK_get_major_firmware_version(); + NK_C_API uint8_t NK_get_major_firmware_version(); /** * Get device's minor firmware version * @return minor part of the version number (e.g. 7 from 0.7, 48 from 0.48 etc.) */ - NK_C_API int NK_get_minor_firmware_version(); + NK_C_API uint8_t NK_get_minor_firmware_version(); /** * Function to determine unencrypted volume PIN type @@ -737,6 +896,17 @@ extern "C" { NK_C_API int NK_get_status_storage(struct NK_storage_status* out); /** + * Get SD card usage attributes. Usable during hidden volumes creation. + * If the command was successful (return value 0), the usage data is + * written to the output pointer’s target. The output pointer must + * not be null. + * Storage only + * @param out the output pointer for the usage data + * @return command processing error code + */ + NK_C_API int NK_get_SD_usage_data(struct NK_SD_usage_data* out); + + /** * Get SD card usage attributes as string. * Usable during hidden volumes creation. * Storage only @@ -747,7 +917,8 @@ extern "C" { /** * Get progress value of current long operation. * Storage only - * @return int in range 0-100 or -1 if device is not busy + * @return int in range 0-100 or -1 if device is not busy or -2 if an + * error occured */ NK_C_API int NK_get_progress_bar_value(); @@ -768,6 +939,19 @@ extern "C" { */ NK_C_API char* NK_list_devices_by_cpuID(); + /** + * Returns a linked list of all connected devices, or null if no devices + * are connected or an error occured. The linked list must be freed by + * calling NK_free_device_info. + * @return a linked list of all connected devices + */ + NK_C_API struct NK_device_info* NK_list_devices(); + + /** + * Free a linked list returned by NK_list_devices. + * @param the linked list to free or null + */ + NK_C_API void NK_free_device_info(struct NK_device_info* device_info); /** * Connects to the device with given ID. ID's list could be created with NK_list_devices_by_cpuID. @@ -780,11 +964,49 @@ extern "C" { NK_C_API int NK_connect_with_ID(const char* id); /** + * Connects to a device with the given path. The path is a USB device + * path as returned by hidapi. + * @param path the device path + * @return 1 on successful connection, 0 otherwise + */ + NK_C_API int NK_connect_with_path(const char* path); + + /** * Blink red and green LED alternatively and infinitely (until device is reconnected). * @return command processing error code */ NK_C_API int NK_wink(); + + /** + * Enable update mode on Nitrokey Pro. + * Supported from v0.11. + * @param update_password 20 bytes update password + * @return command processing error code + */ + NK_C_API int NK_enable_firmware_update_pro(const char* update_password); + + /** + * Change update-mode password on Nitrokey Pro. + * Supported from v0.11. + * @param current_firmware_password 20 bytes update password + * @param new_firmware_password 20 bytes update password + * @return command processing error code + */ + NK_C_API int NK_change_firmware_password_pro(const char *current_firmware_password, const char *new_firmware_password); + + +// as in ReadSlot::ResponsePayload +struct ReadSlot_t { + uint8_t slot_name[15]; + uint8_t _slot_config; + uint8_t slot_token_id[13]; + uint64_t slot_counter; +}; + + +NK_C_API int NK_read_HOTP_slot(const uint8_t slot_num, struct ReadSlot_t* out); + #ifdef __cplusplus } #endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/NitrokeyManager.cc b/nitrokey-sys/libnitrokey-v3.5/NitrokeyManager.cc index a950e4b..6c26a43 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/NitrokeyManager.cc +++ b/nitrokey-sys/libnitrokey-v3.5/NitrokeyManager.cc @@ -29,6 +29,7 @@ #include "libnitrokey/misc.h" #include <mutex> #include "libnitrokey/cxx_semantics.h" +#include "libnitrokey/misc.h" #include <functional> #include <stick10_commands.h> @@ -105,11 +106,10 @@ using nitrokey::misc::strcpyT; return true; } - std::vector<std::string> NitrokeyManager::list_devices(){ + std::vector<DeviceInfo> NitrokeyManager::list_devices(){ std::lock_guard<std::mutex> lock(mex_dev_com_manager); - auto p = make_shared<Stick20>(); - return p->enumerate(); // make static + return Device::enumerate(); } std::vector<std::string> NitrokeyManager::list_devices_by_cpuID(){ @@ -127,11 +127,13 @@ using nitrokey::misc::strcpyT; LOGD1("Enumerating devices"); std::vector<std::string> res; - auto d = make_shared<Stick20>(); - const auto v = d->enumerate(); + const auto v = Device::enumerate(); LOGD1("Discovering IDs"); - for (auto & p: v){ - d = make_shared<Stick20>(); + for (auto & i: v){ + if (i.m_deviceModel != DeviceModel::STORAGE) + continue; + auto p = i.m_path; + auto d = make_shared<Stick20>(); LOGD1( std::string("Found: ") + p ); d->set_path(p); try{ @@ -215,7 +217,26 @@ using nitrokey::misc::strcpyT; } } - auto p = make_shared<Stick20>(); + auto info_ptr = hid_enumerate(NITROKEY_VID, 0); + auto first_info_ptr = info_ptr; + if (!info_ptr) + return false; + + misc::Option<DeviceModel> model; + while (info_ptr && !model.has_value()) { + if (path == std::string(info_ptr->path)) { + model = product_id_to_model(info_ptr->product_id); + } + info_ptr = info_ptr->next; + } + hid_free_enumeration(first_info_ptr); + + if (!model.has_value()) + return false; + + auto p = Device::create(model.value()); + if (!p) + return false; p->set_path(path); if(!p->connect()) return false; @@ -422,6 +443,7 @@ using nitrokey::misc::strcpyT; return ""; } + bool NitrokeyManager::is_internal_hotp_slot_number(uint8_t slot_number) const { return slot_number < 0x20; } bool NitrokeyManager::is_valid_hotp_slot_number(uint8_t slot_number) const { return slot_number < 3; } bool NitrokeyManager::is_valid_totp_slot_number(uint8_t slot_number) const { return slot_number < 0x10-1; } //15 uint8_t NitrokeyManager::get_internal_slot_number_for_totp(uint8_t slot_number) const { return (uint8_t) (0x20 + slot_number); } @@ -885,16 +907,16 @@ using nitrokey::misc::strcpyT; //authorization command is supported for versions equal or below: auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ {DeviceModel::PRO, 7}, - {DeviceModel::STORAGE, 999}, + {DeviceModel::STORAGE, 53}, }); return get_minor_firmware_version() <= m[device->get_device_model()]; } bool NitrokeyManager::is_320_OTP_secret_supported(){ - //authorization command is supported for versions equal or below: + // 320 bit OTP secret is supported by version bigger or equal to: auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ {DeviceModel::PRO, 8}, - {DeviceModel::STORAGE, 999}, + {DeviceModel::STORAGE, 54}, }); return get_minor_firmware_version() >= m[device->get_device_model()]; } @@ -916,7 +938,7 @@ using nitrokey::misc::strcpyT; return false; } - int NitrokeyManager::get_minor_firmware_version(){ + uint8_t NitrokeyManager::get_minor_firmware_version(){ switch(device->get_device_model()){ case DeviceModel::PRO:{ auto status_p = GetStatus::CommandTransaction::run(device); @@ -932,7 +954,7 @@ using nitrokey::misc::strcpyT; } return 0; } - int NitrokeyManager::get_major_firmware_version(){ + uint8_t NitrokeyManager::get_major_firmware_version(){ switch(device->get_device_model()){ case DeviceModel::PRO:{ auto status_p = GetStatus::CommandTransaction::run(device); @@ -1099,11 +1121,31 @@ using nitrokey::misc::strcpyT; return get_TOTP_code(slot_number, 0, 0, 0, user_temporary_password); } + /** + * Returns ReadSlot structure, describing OTP slot configuration. Always return binary counter - + * does the necessary conversion, if needed, to unify the behavior across Pro and Storage. + * @private For internal use only + * @param slot_number which OTP slot to use (usual format) + * @return ReadSlot structure + */ stick10::ReadSlot::ResponsePayload NitrokeyManager::get_OTP_slot_data(const uint8_t slot_number) { auto p = get_payload<stick10::ReadSlot>(); p.slot_number = slot_number; + p.data_format = stick10::ReadSlot::CounterFormat::BINARY; // ignored for devices other than Storage v0.54+ auto data = stick10::ReadSlot::CommandTransaction::run(device, p); - return data.data(); + + auto &payload = data.data(); + + // if fw <=v0.53 and asked binary - do the conversion from ASCII + if (device->get_device_model() == DeviceModel::STORAGE && get_minor_firmware_version() <= 53 + && is_internal_hotp_slot_number(slot_number)) + { + //convert counter from string to ull + auto counter_s = std::string(payload.slot_counter_s, payload.slot_counter_s + sizeof(payload.slot_counter_s)); + payload.slot_counter = std::stoull(counter_s); + } + + return payload; } stick10::ReadSlot::ResponsePayload NitrokeyManager::get_TOTP_slot_data(const uint8_t slot_number) { @@ -1111,13 +1153,7 @@ using nitrokey::misc::strcpyT; } stick10::ReadSlot::ResponsePayload NitrokeyManager::get_HOTP_slot_data(const uint8_t slot_number) { - auto slot_data = get_OTP_slot_data(get_internal_slot_number_for_hotp(slot_number)); - if (device->get_device_model() == DeviceModel::STORAGE){ - //convert counter from string to ull - auto counter_s = std::string(slot_data.slot_counter_s, slot_data.slot_counter_s+sizeof(slot_data.slot_counter_s)); - slot_data.slot_counter = std::stoull(counter_s); - } - return slot_data; + return get_OTP_slot_data(get_internal_slot_number_for_hotp(slot_number)); } void NitrokeyManager::lock_encrypted_volume() { @@ -1146,4 +1182,18 @@ using nitrokey::misc::strcpyT; return data.data(); }; + void NitrokeyManager::enable_firmware_update_pro(const char *firmware_pin) { + auto p = get_payload<FirmwareUpdate>(); + strcpyT(p.firmware_password, firmware_pin); + FirmwareUpdate::CommandTransaction::run(device, p); + } + + void + NitrokeyManager::change_firmware_update_password_pro(const char *firmware_pin_current, const char *firmware_pin_new) { + auto p = get_payload<FirmwarePasswordChange>(); + strcpyT(p.firmware_password_current, firmware_pin_current); + strcpyT(p.firmware_password_new, firmware_pin_new); + FirmwarePasswordChange::CommandTransaction::run(device, p); + } + } diff --git a/nitrokey-sys/libnitrokey-v3.4.1/README.md b/nitrokey-sys/libnitrokey-v3.5/README.md index 81b367a..a3683c0 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/README.md +++ b/nitrokey-sys/libnitrokey-v3.5/README.md @@ -4,7 +4,7 @@ # libnitrokey libnitrokey is a project to communicate with Nitrokey Pro and Storage devices in a clean and easy manner. Written in C++14, testable with `py.test` and `Catch` frameworks, with C API, Python access (through CFFI and C API, in future with Pybind11). -The development of this project is aimed to make it itself a living documentation of communication protocol between host and the Nitrokey stick devices. The command packets' format is described here: [Pro v0.7](include/stick10_commands.h), [Pro v0.8](include/stick10_commands_0.8.h), [Storage](include/stick20_commands.h). Handling and additional operations are described here: [NitrokeyManager.cc](NitrokeyManager.cc). +The development of this project is aimed to make it itself a living documentation of communication protocol between host and the Nitrokey stick devices. The command packets' format is described here: [Pro v0.7](libnitrokey/stick10_commands.h), [Pro v0.8](libnitrokey/stick10_commands_0.8.h), [Storage](libnitrokey/stick20_commands.h). Handling and additional operations are described here: [NitrokeyManager.cc](NitrokeyManager.cc). A C++14 complying compiler is required due to heavy use of variable templates. For feature support tables please check [table 1](https://gcc.gnu.org/projects/cxx-status.html#cxx14) or [table 2](http://en.cppreference.com/w/cpp/compiler_support). @@ -44,7 +44,7 @@ qmake .. make -j2 ``` -### Windows MS Visual Studio 2017 +### Windows and Visual Studio 2017 Lately Visual Studio has started handling CMake files directly. After opening the project's directory it should recognize it and initialize build system. Afterwards please run: 1. `CMake -> Cache -> View Cache CMakeLists.txt -> CMakeLists.txt` to edit settings 2. `CMake -> Build All` to build @@ -76,6 +76,14 @@ Other build options (all take either `ON` or `OFF`): * NO_LOG (default: OFF) - do not compile LOG statements - will make library smaller, but without any diagnostic messages +### Meson +It is possible to use Meson and Ninja to build the project as well (currently available only `master` branch). +Please run: +``` +meson builddir <OPTIONS> +meson configure builddir # to show available build flags +ninja -C builddir +``` # Using libnitrokey with Python To use libnitrokey with Python a [CFFI](http://cffi.readthedocs.io/en/latest/overview.html) library is required (either 2.7+ or 3.0+). It can be installed with: @@ -155,25 +163,31 @@ In case no devices are connected, a friendly message will be printed. All available functions for C and Python are listed in [NK_C_API.h](NK_C_API.h). Please check `Documentation` section below. ## Documentation -The documentation of C API is included in the sources (could be generated with doxygen if requested). -Please check [NK_C_API.h](NK_C_API.h) (C API) for high level commands and [include/NitrokeyManager.h](include/NitrokeyManager.h) (C++ API). All devices' commands are listed along with packet format in [include/stick10_commands.h](include/stick10_commands.h) and [include/stick20_commands.h](include/stick20_commands.h) respectively for Nitrokey Pro and Nitrokey Storage products. +The documentation of C API is included in the sources (can be generated with `make doc` if Doxygen is installed). +Please check [NK_C_API.h](NK_C_API.h) (C API) for high level commands and [libnitrokey/NitrokeyManager.h](libnitrokey/NitrokeyManager.h) (C++ API). All devices' commands are listed along with packet format in [libnitrokey/stick10_commands.h](libnitrokey/stick10_commands.h) and [libnitrokey/stick20_commands.h](libnitrokey/stick20_commands.h) respectively for Nitrokey Pro and Nitrokey Storage products. # Tests -Warning! Before you run unittests please either change both your Admin and User PINs on your Nitrostick to defaults (`12345678` and `123456` respectively) or change the values in tests source code. If you do not change them the tests might lock your device and lose your data. If it's too late, you can reset your Nitrokey using instructions from [homepage](https://www.nitrokey.com/de/documentation/how-reset-nitrokey). +**Warning!** Most of the tests will overwrite user data. The only user-data safe tests are specified in `unittest/test_safe.cpp` (see *C++ tests* chapter). + +**Warning!** Before you run unittests please change both your Admin and User PINs on your Nitrostick to defaults (`12345678` and `123456` respectively), or change the values in tests source code. If you do not change them, the tests might lock your device temporarily. If it's too late already, you can reset your Nitrokey using instructions from [homepage](https://www.nitrokey.com/de/documentation/how-reset-nitrokey). ## Python tests -libnitrokey has a great suite of tests written in Python 3 under the path: `unittest/test_*.py`: +libnitrokey has a great suite of tests written in Python 3 under the path: [unittest/test_*.py](https://github.com/Nitrokey/libnitrokey/tree/master/unittest): * `test_pro.py` - contains tests of OTP, Password Safe and PIN control functionality. Could be run on both Pro and Storage devices. * `test_storage.py` - contains tests of Encrypted Volumes functionality. Could be run only on Storage. + The tests themselves show how to handle common requests to device. Before running please install all required libraries with: ```bash cd unittest pip install --user -r requirements.txt ``` +or use Python's environment managing tool like [pipenv](https://pipenv.readthedocs.io/en/latest/) or `virtualenv`. + + To run them please execute: ```bash -# substitute <dev> with either pro or storage +# substitute <dev> with either 'pro' or 'storage' py.test -v test_<dev>.py # more specific use - run tests containing in name <test_name> 5 times: py.test -v test_<dev>.py -k <test_name> --count 5 @@ -182,9 +196,76 @@ py.test -v test_<dev>.py -k <test_name> --count 5 For additional documentation please check the following for [py.test installation](http://doc.pytest.org/en/latest/getting-started.html). For better coverage [randomly plugin](https://pypi.python.org/pypi/pytest-randomly) is installed - it randomizes the test order allowing to detect unseen dependencies between the tests. ## C++ tests -There are also some unit tests implemented in C++, placed in unittest directory. They are not written as extensively as Python tests and are rather more a C++ low level interface check, often not using C++ API from `NitrokeyManager.cc`. Some of them are: [test_HOTP.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_HOTP.cc), -[test.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test.cc). -Unit tests were written and tested on Ubuntu 16.04/16.10/17.04. To run them just execute binaries built in ./libnitrokey/build dir after enabling them by passing `-DCOMPILE_TESTS=ON` option like in `cmake .. -DCOMPILE_TESTS=ON && make`. +There are also some unit tests implemented in C++, placed in unittest directory. The only user-data safe online test set here is [test_safe.cpp](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_safe.cpp), which tries to connect to the device, and collect its status data. Example run for Storage: +```text +# Storage device inserted, firmware version v0.53 +$ ./test_safe +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_DEVICE_STATUS +.. +[Wed Jan 2 13:31:17 2019][DEBUG_L1] <= GET_DEVICE_STATUS 0 1 +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_PASSWORD_RETRY_COUNT +[Wed Jan 2 13:31:17 2019][DEBUG_L1] <= GET_PASSWORD_RETRY_COUNT 0 0 +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_DEVICE_STATUS +.. +[Wed Jan 2 13:31:17 2019][DEBUG_L1] <= GET_DEVICE_STATUS 0 1 +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_USER_PASSWORD_RETRY_COUNT +[Wed Jan 2 13:31:17 2019][DEBUG_L1] <= GET_USER_PASSWORD_RETRY_COUNT 0 0 +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_DEVICE_STATUS +... +[Wed Jan 2 13:31:17 2019][DEBUG_L1] <= GET_DEVICE_STATUS 0 1 + transmission_data.dissect(): _padding: +0000 00 00 00 00 00 00 00 00 00 00 00 00 00 05 2e 01 ................ +0010 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- .. + (int) SendCounter_u8: 0 + (int) SendDataType_u8: 3 + (int) FollowBytesFlag_u8: 0 + (int) SendSize_u8: 28 + + MagicNumber_StickConfig_u16: 13080 + (int) ReadWriteFlagUncryptedVolume_u8: 1 + (int) ReadWriteFlagCryptedVolume_u8: 0 + (int) ReadWriteFlagHiddenVolume_u8: 0 + (int) versionInfo.major: 0 + (int) versionInfo.minor: 53 + (int) versionInfo.build_iteration: 0 + (int) FirmwareLocked_u8: 0 + (int) NewSDCardFound_u8: 1 + (int) NewSDCardFound_st.NewCard: 1 + (int) NewSDCardFound_st.Counter: 0 + (int) SDFillWithRandomChars_u8: 1 + ActiveSD_CardID_u32: 3670817656 + (int) VolumeActiceFlag_u8: 1 + (int) VolumeActiceFlag_st.unencrypted: 1 + (int) VolumeActiceFlag_st.encrypted: 0 + (int) VolumeActiceFlag_st.hidden: 0 + (int) NewSmartCardFound_u8: 0 + (int) UserPwRetryCount: 3 + (int) AdminPwRetryCount: 3 + ActiveSmartCardID_u32: 24122 + (int) StickKeysNotInitiated: 0 + +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_DEVICE_STATUS +.. +[Wed Jan 2 13:31:17 2019][DEBUG_L1] <= GET_DEVICE_STATUS 0 1 +00005e3a +[Wed Jan 2 13:31:17 2019][DEBUG_L1] => GET_DEVICE_STATUS +.... +[Wed Jan 2 13:31:18 2019][DEBUG_L1] <= GET_DEVICE_STATUS 0 1 +[Wed Jan 2 13:31:18 2019][DEBUG_L1] => GET_DEVICE_STATUS +... +[Wed Jan 2 13:31:18 2019][DEBUG_L1] <= GET_DEVICE_STATUS 0 1 +=============================================================================== +All tests passed (18 assertions in 6 test cases) +``` +Test's execution configuration and verbosity could be manipulated - please see `./test_safe --help` for details. + +The other tests sets are not written as extensively as Python tests and are rather more a C++ low level interface check used during the library development, using either low-level components, C API from `NK_C_API.cc`, or C++ API from `NitrokeyManager.cc`. Some of them are: [test_HOTP.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_HOTP.cc), +[test1.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test1.cc). See more in [unittest](https://github.com/Nitrokey/libnitrokey/tree/master/unittest) directory. + +**Note: these are not device model agnostic, and will most probably destroy your data on the device.** + + +Unit tests were checked on Ubuntu 16.04/16.10/17.04. To run them just execute binaries built in `./libnitrokey/build` dir, after enabling them by passing `-DCOMPILE_TESTS=ON` option to `cmake` - e.g.: `cmake .. -DCOMPILE_TESTS=ON && make`. The documentation of how it works could be found in nitrokey-app project's README on Github: @@ -198,7 +279,7 @@ firmware code should show how things works: # Known issues / tasks * Currently only one device can be connected at a time (experimental work could be found in `wip-multiple_devices` branch), -* C++ API needs some reorganization to C++ objects (instead of pointers to arrays). This will be also preparing for integration with Pybind11, +* C++ API needs some reorganization to C++ objects (instead of pointers to byte arrays). This will be also preparing for integration with Pybind11, * Fix compilation warnings. Other tasks might be listed either in [TODO](TODO) file or on project's issues page. diff --git a/nitrokey-sys/libnitrokey-v3.4.1/command_id.cc b/nitrokey-sys/libnitrokey-v3.5/command_id.cc index a6c2a28..9a329bc 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/command_id.cc +++ b/nitrokey-sys/libnitrokey-v3.5/command_id.cc @@ -71,6 +71,10 @@ const char *commandid_to_string(CommandID id) { return "CHANGE_USER_PIN"; case CommandID::CHANGE_ADMIN_PIN: return "CHANGE_ADMIN_PIN"; + case CommandID::FIRMWARE_UPDATE: + return "FIRMWARE_UPDATE"; + case CommandID::FIRMWARE_PASSWORD_CHANGE: + return "FIRMWARE_PASSWORD_CHANGE"; case CommandID::ENABLE_CRYPTED_PARI: return "ENABLE_CRYPTED_PARI"; diff --git a/nitrokey-sys/libnitrokey-v3.4.1/device.cc b/nitrokey-sys/libnitrokey-v3.5/device.cc index 80e4b38..bc42965 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/device.cc +++ b/nitrokey-sys/libnitrokey-v3.5/device.cc @@ -20,7 +20,9 @@ */ #include <chrono> +#include <codecvt> #include <iostream> +#include <locale> #include <thread> #include <cstddef> #include <stdexcept> @@ -36,11 +38,42 @@ std::mutex mex_dev_com; using namespace nitrokey::device; using namespace nitrokey::log; +using namespace nitrokey::misc; using namespace std::chrono; +const uint16_t nitrokey::device::NITROKEY_VID = 0x20a0; +const uint16_t nitrokey::device::NITROKEY_PRO_PID = 0x4108; +const uint16_t nitrokey::device::NITROKEY_STORAGE_PID = 0x4109; + +Option<DeviceModel> nitrokey::device::product_id_to_model(uint16_t product_id) { + switch (product_id) { + case NITROKEY_PRO_PID: + return DeviceModel::PRO; + case NITROKEY_STORAGE_PID: + return DeviceModel::STORAGE; + default: + return {}; + } +} + std::atomic_int Device::instances_count{0}; std::chrono::milliseconds Device::default_delay {0} ; +std::ostream& nitrokey::device::operator<<(std::ostream& stream, DeviceModel model) { + switch (model) { + case DeviceModel::PRO: + stream << "Pro"; + break; + case DeviceModel::STORAGE: + stream << "Storage"; + break; + default: + stream << "Unknown"; + break; + } + return stream; +} + Device::Device(const uint16_t vid, const uint16_t pid, const DeviceModel model, const milliseconds send_receive_delay, const int retry_receiving_count, const milliseconds retry_timeout) @@ -171,14 +204,20 @@ int Device::recv(void *packet) { return status; } -std::vector<std::string> Device::enumerate(){ - //TODO make static - auto pInfo = hid_enumerate(m_vid, m_pid); +std::vector<DeviceInfo> Device::enumerate(){ + auto pInfo = hid_enumerate(NITROKEY_VID, 0); auto pInfo_ = pInfo; - std::vector<std::string> res; + std::vector<DeviceInfo> res; while (pInfo != nullptr){ - std::string a (pInfo->path); - res.push_back(a); + auto deviceModel = product_id_to_model(pInfo->product_id); + if (deviceModel.has_value()) { + std::string path(pInfo->path); + std::wstring serialNumberW(pInfo->serial_number); + std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; + std::string serialNumber = converter.to_bytes(serialNumberW); + DeviceInfo info = { deviceModel.value(), path, serialNumber }; + res.push_back(info); + } pInfo = pInfo->next; } @@ -189,6 +228,17 @@ std::vector<std::string> Device::enumerate(){ return res; } +std::shared_ptr<Device> Device::create(DeviceModel model) { + switch (model) { + case DeviceModel::PRO: + return std::make_shared<Stick10>(); + case DeviceModel::STORAGE: + return std::make_shared<Stick20>(); + default: + return {}; + } +} + bool Device::could_be_enumerated() { LOG(__FUNCTION__, Loglevel::DEBUG_L2); std::lock_guard<std::mutex> lock(mex_dev_com); @@ -243,14 +293,14 @@ void Device::set_retry_delay(const std::chrono::milliseconds delay){ } Stick10::Stick10(): - Device(0x20a0, 0x4108, DeviceModel::PRO, 100ms, 5, 100ms) + Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 5, 100ms) { setDefaultDelay(); } Stick20::Stick20(): - Device(0x20a0, 0x4109, DeviceModel::STORAGE, 40ms, 55, 40ms) + Device(NITROKEY_VID, NITROKEY_STORAGE_PID, DeviceModel::STORAGE, 40ms, 55, 40ms) { setDefaultDelay(); } diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/CommandFailedException.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/CommandFailedException.h index 32bd6b7..32bd6b7 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/CommandFailedException.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/CommandFailedException.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/DeviceCommunicationExceptions.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/DeviceCommunicationExceptions.h index f710d0b..f710d0b 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/DeviceCommunicationExceptions.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/DeviceCommunicationExceptions.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LibraryException.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/LibraryException.h index 3b9d177..3b9d177 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LibraryException.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/LibraryException.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LongOperationInProgressException.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/LongOperationInProgressException.h index 865d6b5..865d6b5 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LongOperationInProgressException.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/LongOperationInProgressException.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/NitrokeyManager.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/NitrokeyManager.h index d6e5df4..33ede1b 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/NitrokeyManager.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/NitrokeyManager.h @@ -80,7 +80,7 @@ char * strndup(const char* str, size_t maxlen); bool get_time(uint64_t time = 0); bool erase_totp_slot(uint8_t slot_number, const char *temporary_password); bool erase_hotp_slot(uint8_t slot_number, const char *temporary_password); - std::vector<std::string> list_devices(); + std::vector<DeviceInfo> list_devices(); std::vector<std::string> list_devices_by_cpuID(); /** @@ -215,7 +215,7 @@ char * strndup(const char* str, size_t maxlen); template <typename S, typename A, typename T> void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device); - int get_minor_firmware_version(); + uint8_t get_minor_firmware_version(); explicit NitrokeyManager(); void set_log_function(std::function<void(std::string)> log_function); @@ -278,7 +278,7 @@ char * strndup(const char* str, size_t maxlen); */ void set_encrypted_volume_read_write(const char *admin_pin); - int get_major_firmware_version(); + uint8_t get_major_firmware_version(); bool is_smartcard_in_use(); @@ -295,6 +295,11 @@ char * strndup(const char* str, size_t maxlen); void wink(); stick20::ProductionTest::ResponsePayload production_info(); + + void enable_firmware_update_pro(const char *firmware_pin); + + void change_firmware_update_password_pro(const char *firmware_pin_current, const char *firmware_pin_new); + bool is_internal_hotp_slot_number(uint8_t slot_number) const; }; } diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/command.h index 6852bf0..6852bf0 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/command.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command_id.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/command_id.h index eb0d450..ee6726c 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command_id.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/command_id.h @@ -88,6 +88,8 @@ enum class CommandID : uint8_t { CHANGE_ADMIN_PIN = 0x15, WRITE_TO_SLOT_2 = 0x16, SEND_OTP_DATA = 0x17, + FIRMWARE_UPDATE = 0x19, + FIRMWARE_PASSWORD_CHANGE = 0x1A, ENABLE_CRYPTED_PARI = 0x20, DISABLE_CRYPTED_PARI = 0x20 + 1, diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/cxx_semantics.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/cxx_semantics.h index 36ed142..36ed142 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/cxx_semantics.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/cxx_semantics.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/deprecated.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/deprecated.h index 5a83288..5a83288 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/deprecated.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/deprecated.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/device.h index f6d2380..d50080d 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/device.h @@ -24,8 +24,11 @@ #include <chrono> #include "hidapi/hidapi.h" #include <cstdint> +#include <memory> #include <string> +#include <ostream> #include <vector> +#include "misc.h" #define HID_REPORT_SIZE 65 @@ -50,6 +53,48 @@ enum class DeviceModel{ STORAGE }; +std::ostream& operator<<(std::ostream& stream, DeviceModel model); + +/** + * The USB vendor ID for Nitrokey devices. + */ +extern const uint16_t NITROKEY_VID; +/** + * The USB product ID for the Nitrokey Pro. + */ +extern const uint16_t NITROKEY_PRO_PID; +/** + * The USB product ID for the Nitrokey Storage. + */ +extern const uint16_t NITROKEY_STORAGE_PID; + +/** + * Convert the given USB product ID to a Nitrokey model. If there is no model + * with that ID, return an absent value. + */ +misc::Option<DeviceModel> product_id_to_model(uint16_t product_id); + +/** + * Information about a connected device. + * + * This struct contains the information about a connected device returned by + * hidapi when enumerating the connected devices. + */ +struct DeviceInfo { + /** + * The model of the connected device. + */ + DeviceModel m_deviceModel; + /** + * The USB connection path for the device. + */ + std::string m_path; + /** + * The serial number of the device. + */ + std::string m_serialNumber; +}; + #include <atomic> class Device { @@ -106,7 +151,17 @@ public: * @return true if visible by OS */ bool could_be_enumerated(); - std::vector<std::string> enumerate(); + /** + * Returns a vector with all connected Nitrokey devices. + * + * @return information about all connected devices + */ + static std::vector<DeviceInfo> enumerate(); + + /** + * Create a Device of the given model. + */ + static std::shared_ptr<Device> create(DeviceModel model); void show_stats(); diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device_proto.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/device_proto.h index 45a6c16..45a6c16 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device_proto.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/device_proto.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/dissect.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/dissect.h index 690b5b7..690b5b7 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/dissect.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/dissect.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/hidapi/hidapi.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/hidapi/hidapi.h index e5bc2dc..e5bc2dc 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/hidapi/hidapi.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/hidapi/hidapi.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/log.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/log.h index 2a64bef..278b49c 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/log.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/log.h @@ -46,6 +46,7 @@ namespace nitrokey { class LogHandler { public: virtual void print(const std::string &, Loglevel lvl) = 0; + virtual ~LogHandler() = default; protected: std::string loglevel_to_str(Loglevel); std::string format_message_to_string(const std::string &str, const Loglevel &lvl); diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/misc.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/misc.h index 88254dd..a9c4672 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/misc.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/misc.h @@ -29,12 +29,37 @@ #include "log.h" #include "LibraryException.h" #include <sstream> +#include <stdexcept> #include <iomanip> namespace nitrokey { namespace misc { +/** + * Simple replacement for std::optional (C++17). + */ +template<typename T> +class Option { +public: + Option() : m_hasValue(false), m_value() {} + Option(T value) : m_hasValue(true), m_value(value) {} + + bool has_value() const { + return m_hasValue; + } + T value() const { + if (!m_hasValue) { + throw std::logic_error("Called Option::value without value"); + } + return m_value; + } + +private: + bool m_hasValue; + T m_value; +}; + template<typename T> std::string toHex(T value){ using namespace std; @@ -42,7 +67,8 @@ namespace misc { oss << std::hex << std::setw(sizeof(value)*2) << std::setfill('0') << value; return oss.str(); } - + +#define FIELD_WIDTH_MAX (100) /** * Copies string from pointer to fixed size C-style array. Src needs to be a valid C-string - eg. ended with '\0'. * Throws when source is bigger than destination. @@ -57,12 +83,13 @@ namespace misc { // throw EmptySourceStringException(slot_number); return; const size_t s_dest = sizeof dest; - LOG(std::string("strcpyT sizes dest src ") - +std::to_string(s_dest)+ " " - +std::to_string(strlen(src))+ " " - ,nitrokey::log::Loglevel::DEBUG_L2); - if (strlen(src) > s_dest){ - throw TooLongStringException(strlen(src), s_dest, src); + const size_t src_strlen = strnlen(src, FIELD_WIDTH_MAX); + LOG(std::string("strcpyT sizes dest src ") + + std::to_string(s_dest) + " " + + std::to_string(src_strlen) + " " + , nitrokey::log::Loglevel::DEBUG_L2); + if (src_strlen > s_dest){ + throw TooLongStringException(src_strlen, s_dest, src); } strncpy((char*) &dest, src, s_dest); } diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick10_commands.h index f2ffba2..5e8a5aa 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick10_commands.h @@ -304,8 +304,13 @@ class GetHOTP : Command<CommandID::GET_CODE> { class ReadSlot : Command<CommandID::READ_SLOT> { public: + enum class CounterFormat { + ASCII = 0, + BINARY = 1, + }; struct CommandPayload { uint8_t slot_number; + CounterFormat data_format; //Storage v0.54+ only: slot_counter value format: 0 - in ascii, 1 - binary bool isValid() const { return !(slot_number & 0xF0); } @@ -882,6 +887,41 @@ class BuildAESKey : Command<CommandID::NEW_AES_KEY> { }; +class FirmwareUpdate : Command<CommandID::FIRMWARE_UPDATE> { +public: + struct CommandPayload { + uint8_t firmware_password[20]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(firmware_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + +}; + +class FirmwarePasswordChange : Command<CommandID::FIRMWARE_PASSWORD_CHANGE> { +public: + struct CommandPayload { + uint8_t firmware_password_current[20]; + uint8_t firmware_password_new[20]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(firmware_password_current); + print_to_ss_volatile(firmware_password_new); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + +}; + + } } } diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands_0.8.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick10_commands_0.8.h index 9477890..9477890 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands_0.8.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick10_commands_0.8.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick20_commands.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick20_commands.h index 7efa1b6..7efa1b6 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick20_commands.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/stick20_commands.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/version.h b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/version.h index 6547af0..6547af0 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/version.h +++ b/nitrokey-sys/libnitrokey-v3.5/libnitrokey/version.h diff --git a/nitrokey-sys/libnitrokey-v3.4.1/log.cc b/nitrokey-sys/libnitrokey-v3.5/log.cc index 06acee7..06acee7 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/log.cc +++ b/nitrokey-sys/libnitrokey-v3.5/log.cc diff --git a/nitrokey-sys/libnitrokey-v3.4.1/misc.cc b/nitrokey-sys/libnitrokey-v3.5/misc.cc index 59185f3..59185f3 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/misc.cc +++ b/nitrokey-sys/libnitrokey-v3.5/misc.cc diff --git a/nitrokey-sys/libnitrokey-v3.4.1/version.cc b/nitrokey-sys/libnitrokey-v3.5/version.cc index dfdc802..dfdc802 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/version.cc +++ b/nitrokey-sys/libnitrokey-v3.5/version.cc diff --git a/nitrokey-sys/libnitrokey-v3.4.1/version.cc.in b/nitrokey-sys/libnitrokey-v3.5/version.cc.in index 0eae647..0eae647 100644 --- a/nitrokey-sys/libnitrokey-v3.4.1/version.cc.in +++ b/nitrokey-sys/libnitrokey-v3.5/version.cc.in diff --git a/nitrokey-sys/src/ffi.rs b/nitrokey-sys/src/ffi.rs index 58879ad..a01eae8 100644 --- a/nitrokey-sys/src/ffi.rs +++ b/nitrokey-sys/src/ffi.rs @@ -1,5 +1,10 @@ /* automatically generated by rust-bindgen, manually modified */ +extern "C" { + #[link_name = "\u{1}NK_PWS_SLOT_COUNT"] + pub static mut NK_PWS_SLOT_COUNT: u8; +} +pub const MAXIMUM_STR_REPLY_LENGTH: ::std::os::raw::c_int = 8192; /// Use, if no supported device is connected pub const NK_device_model_NK_DISCONNECTED: NK_device_model = 0; /// Nitrokey Pro. @@ -8,6 +13,188 @@ pub const NK_device_model_NK_PRO: NK_device_model = 1; pub const NK_device_model_NK_STORAGE: NK_device_model = 2; /// The Nitrokey device models supported by the API. pub type NK_device_model = u32; +/// The connection info for a Nitrokey device as a linked list. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NK_device_info { + /// The model of the Nitrokey device. + pub model: NK_device_model, + /// The USB device path for NK_connect_with_path. + pub path: *mut ::std::os::raw::c_char, + /// The serial number. + pub serial_number: *mut ::std::os::raw::c_char, + /// The pointer to the next element of the linked list or null + /// if this is the last element in the list. + pub next: *mut NK_device_info, +} +#[test] +fn bindgen_test_layout_NK_device_info() { + assert_eq!( + ::std::mem::size_of::<NK_device_info>(), + 32usize, + concat!("Size of: ", stringify!(NK_device_info)) + ); + assert_eq!( + ::std::mem::align_of::<NK_device_info>(), + 8usize, + concat!("Alignment of ", stringify!(NK_device_info)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_device_info>())).model as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(NK_device_info), + "::", + stringify!(model) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_device_info>())).path as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(NK_device_info), + "::", + stringify!(path) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_device_info>())).serial_number as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(NK_device_info), + "::", + stringify!(serial_number) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_device_info>())).next as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(NK_device_info), + "::", + stringify!(next) + ) + ); +} +/// Stores the common device status for all Nitrokey devices. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NK_status { + /// The major firmware version, e. g. 0 in v0.40. + pub firmware_version_major: u8, + /// The minor firmware version, e. g. 40 in v0.40. + pub firmware_version_minor: u8, + /// The serial number of the smart card. + pub serial_number_smart_card: u32, + /// The HOTP slot to generate a password from if the numlock + /// key is pressed twice (slot 0-1, or any other value to + /// disable the function). + pub config_numlock: u8, + /// The HOTP slot to generate a password from if the capslock + /// key is pressed twice (slot 0-1, or any other value to + /// disable the function). + pub config_capslock: u8, + /// The HOTP slot to generate a password from if the scrolllock + /// key is pressed twice (slot 0-1, or any other value to + /// disable the function). + pub config_scrolllock: u8, + /// Indicates whether the user password is required to generate + /// an OTP value. + pub otp_user_password: bool, +} +#[test] +fn bindgen_test_layout_NK_status() { + assert_eq!( + ::std::mem::size_of::<NK_status>(), + 12usize, + concat!("Size of: ", stringify!(NK_status)) + ); + assert_eq!( + ::std::mem::align_of::<NK_status>(), + 4usize, + concat!("Alignment of ", stringify!(NK_status)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_status>())).firmware_version_major as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(firmware_version_major) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_status>())).firmware_version_minor as *const _ as usize + }, + 1usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(firmware_version_minor) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_status>())).serial_number_smart_card as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(serial_number_smart_card) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_status>())).config_numlock as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(config_numlock) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_status>())).config_capslock as *const _ as usize }, + 9usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(config_capslock) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_status>())).config_scrolllock as *const _ as usize }, + 10usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(config_scrolllock) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<NK_status>())).otp_user_password as *const _ as usize }, + 11usize, + concat!( + "Offset of field: ", + stringify!(NK_status), + "::", + stringify!(otp_user_password) + ) + ); +} /// Stores the status of a Storage device. #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -259,6 +446,54 @@ fn bindgen_test_layout_NK_storage_status() { ) ); } +/// Data about the usage of the SD card. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NK_SD_usage_data { + /// The minimum write level, as a percentage of the total card + /// size. + pub write_level_min: u8, + /// The maximum write level, as a percentage of the total card + /// size. + pub write_level_max: u8, +} +#[test] +fn bindgen_test_layout_NK_SD_usage_data() { + assert_eq!( + ::std::mem::size_of::<NK_SD_usage_data>(), + 2usize, + concat!("Size of: ", stringify!(NK_SD_usage_data)) + ); + assert_eq!( + ::std::mem::align_of::<NK_SD_usage_data>(), + 1usize, + concat!("Alignment of ", stringify!(NK_SD_usage_data)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_SD_usage_data>())).write_level_min as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(NK_SD_usage_data), + "::", + stringify!(write_level_min) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_SD_usage_data>())).write_level_max as *const _ as usize + }, + 1usize, + concat!( + "Offset of field: ", + stringify!(NK_SD_usage_data), + "::", + stringify!(write_level_max) + ) + ); +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NK_storage_ProductionTest { @@ -520,9 +755,28 @@ extern "C" { pub fn NK_get_device_model() -> NK_device_model; } extern "C" { + /// Return the debug status string. Debug purposes. This function is + /// deprecated in favor of NK_get_status_as_string. + /// @return string representation of the status or an empty string + /// if the command failed + #[deprecated(since = "3.5.0", note="use `NK_get_status_as_string` instead")] + pub fn NK_status() -> *mut ::std::os::raw::c_char; +} +extern "C" { /// Return the debug status string. Debug purposes. + /// @return string representation of the status or an empty string + /// if the command failed + pub fn NK_get_status_as_string() -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get the stick status common to all Nitrokey devices and return the + /// command processing error code. If the code is zero, i. e. the + /// command was successful, the storage status is written to the output + /// pointer's target. The output pointer must not be null. + /// + /// @param out the output pointer for the status /// @return command processing error code - pub fn NK_status() -> *mut ::std::os::raw::c_char; + pub fn NK_get_status(out: *mut NK_status) -> ::std::os::raw::c_int; } extern "C" { /// Return the device's serial number string in hex. @@ -760,6 +1014,7 @@ extern "C" { pub fn NK_totp_set_time_soft(time: u64) -> ::std::os::raw::c_int; } extern "C" { + #[deprecated(since = "3.4.0", note="use `NK_totp_set_time_soft` instead")] pub fn NK_totp_get_time() -> ::std::os::raw::c_int; } extern "C" { @@ -853,12 +1108,12 @@ extern "C" { extern "C" { /// Get device's major firmware version /// @return major part of the version number (e.g. 0 from 0.48, 0 from 0.7 etc.) - pub fn NK_get_major_firmware_version() -> ::std::os::raw::c_int; + pub fn NK_get_major_firmware_version() -> u8; } extern "C" { /// Get device's minor firmware version /// @return minor part of the version number (e.g. 7 from 0.7, 48 from 0.48 etc.) - pub fn NK_get_minor_firmware_version() -> ::std::os::raw::c_int; + pub fn NK_get_minor_firmware_version() -> u8; } extern "C" { /// Function to determine unencrypted volume PIN type @@ -1068,6 +1323,16 @@ extern "C" { pub fn NK_get_status_storage(out: *mut NK_storage_status) -> ::std::os::raw::c_int; } extern "C" { + /// Get SD card usage attributes. Usable during hidden volumes creation. + /// If the command was successful (return value 0), the usage data is + /// written to the output pointer’s target. The output pointer must + /// not be null. + /// Storage only + /// @param out the output pointer for the usage data + /// @return command processing error code + pub fn NK_get_SD_usage_data(out: *mut NK_SD_usage_data) -> ::std::os::raw::c_int; +} +extern "C" { /// Get SD card usage attributes as string. /// Usable during hidden volumes creation. /// Storage only @@ -1077,7 +1342,8 @@ extern "C" { extern "C" { /// Get progress value of current long operation. /// Storage only - /// @return int in range 0-100 or -1 if device is not busy + /// @return int in range 0-100 or -1 if device is not busy or -2 if an + /// error occured pub fn NK_get_progress_bar_value() -> ::std::os::raw::c_int; } extern "C" { @@ -1097,6 +1363,18 @@ extern "C" { pub fn NK_list_devices_by_cpuID() -> *mut ::std::os::raw::c_char; } extern "C" { + /// Returns a linked list of all connected devices, or null if no devices + /// are connected or an error occured. The linked list must be freed by + /// calling NK_free_device_info. + /// @return a linked list of all connected devices + pub fn NK_list_devices() -> *mut NK_device_info; +} +extern "C" { + /// Free a linked list returned by NK_list_devices. + /// @param the linked list to free or null + pub fn NK_free_device_info(device_info: *mut NK_device_info); +} +extern "C" { /// Connects to the device with given ID. ID's list could be created with NK_list_devices_by_cpuID. /// Requires calling to NK_list_devices_by_cpuID first. Connecting to arbitrary ID/USB path is not handled. /// On connection requests status from device and disconnects it / removes from map on connection failure. @@ -1106,7 +1384,98 @@ extern "C" { pub fn NK_connect_with_ID(id: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; } extern "C" { + /// Connects to a device with the given path. The path is a USB device + /// path as returned by hidapi. + /// @param path the device path + /// @return 1 on successful connection, 0 otherwise + pub fn NK_connect_with_path(path: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { /// Blink red and green LED alternatively and infinitely (until device is reconnected). /// @return command processing error code pub fn NK_wink() -> ::std::os::raw::c_int; } +extern "C" { + /// Enable update mode on Nitrokey Pro. + /// Supported from v0.11. + /// @param update_password 20 bytes update password + /// @return command processing error code + pub fn NK_enable_firmware_update_pro( + update_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Change update-mode password on Nitrokey Pro. + /// Supported from v0.11. + /// @param current_firmware_password 20 bytes update password + /// @param new_firmware_password 20 bytes update password + /// @return command processing error code + pub fn NK_change_firmware_password_pro( + current_firmware_password: *const ::std::os::raw::c_char, + new_firmware_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ReadSlot_t { + pub slot_name: [u8; 15usize], + pub _slot_config: u8, + pub slot_token_id: [u8; 13usize], + pub slot_counter: u64, +} +#[test] +fn bindgen_test_layout_ReadSlot_t() { + assert_eq!( + ::std::mem::size_of::<ReadSlot_t>(), + 40usize, + concat!("Size of: ", stringify!(ReadSlot_t)) + ); + assert_eq!( + ::std::mem::align_of::<ReadSlot_t>(), + 8usize, + concat!("Alignment of ", stringify!(ReadSlot_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<ReadSlot_t>())).slot_name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(ReadSlot_t), + "::", + stringify!(slot_name) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<ReadSlot_t>()))._slot_config as *const _ as usize }, + 15usize, + concat!( + "Offset of field: ", + stringify!(ReadSlot_t), + "::", + stringify!(_slot_config) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<ReadSlot_t>())).slot_token_id as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(ReadSlot_t), + "::", + stringify!(slot_token_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<ReadSlot_t>())).slot_counter as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(ReadSlot_t), + "::", + stringify!(slot_counter) + ) + ); +} +extern "C" { + pub fn NK_read_HOTP_slot(slot_num: u8, out: *mut ReadSlot_t) -> ::std::os::raw::c_int; +} diff --git a/nitrokey/CHANGELOG.md b/nitrokey/CHANGELOG.md index e98e857..3051d0f 100644 --- a/nitrokey/CHANGELOG.md +++ b/nitrokey/CHANGELOG.md @@ -38,6 +38,16 @@ SPDX-License-Identifier: MIT - Implement `DerefMut` for `User<T>` and `Admin<T>`. - Add `device_mut` method to `DeviceWrapper`. - Require a mutable `Device` reference if a method changes the device state. +- Update the `nitrokey-sys` dependency to version 3.5.0. +- Update the `nitrokey-test` dependency to version 0.3 and add the + `nitrokey-test-state` dependency in version 0.1.0. +- Refactor connection management: + - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. + - Add the `Manager` struct that manages connections to Nitrokey devices. + - Remove `connect`, `connect_model`, `Pro::connect` and `Storage::connect`. + - Add the `into_manager` function to the `Device` trait. + - Add the `force_take` function that ignores a `PoisonError` when accessing + the manager instance. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/nitrokey/Cargo.toml b/nitrokey/Cargo.toml index fd6fef7..62eea02 100644 --- a/nitrokey/Cargo.toml +++ b/nitrokey/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nitrokey" -version = "0.4.0-alpha.2" +version = "0.4.0-alpha.3" authors = ["Robin Krahl <robin.krahl@ireas.org>"] edition = "2018" homepage = "https://code.ireas.org/nitrokey-rs/" @@ -17,10 +17,12 @@ license = "MIT" exclude = [".builds/*"] [dependencies] +lazy_static = "1.2.0" libc = "0.2" -nitrokey-sys = "~3.4" +nitrokey-sys = "3.5" rand_core = {version = "0.3", default-features = false, features = ["std"] } rand_os = {version = "0.1"} [dev-dependencies] -nitrokey-test = {version = "=0.2.0"} +nitrokey-test = "0.3" +nitrokey-test-state = "0.1.0" diff --git a/nitrokey/README.md b/nitrokey/README.md index 8c596eb..a29ac6f 100644 --- a/nitrokey/README.md +++ b/nitrokey/README.md @@ -28,13 +28,6 @@ supported by `nitrokey-rs`: - `NK_get_device_model`. We know which model we connected to, so we can provide this information without calling `libnitrokey`. -- `NK_get_time`. This method is useless as it will always cause a timestamp - error on the device (see [pull request #114][] for `libnitrokey` for details). -- `NK_get_status`. This method only provides a string representation of - data that can be accessed by other methods (firmware version, serial number, - configuration). -- `NK_get_status_storage_as_string`. This method only provides an incomplete - string representation of the data returned by `NK_get_status_storage`. - `NK_is_AES_supported`. This method is no longer needed for Nitrokey devices with a recent firmware version. - `NK_set_unencrypted_volume_rorw_pin_type_user`, @@ -42,6 +35,11 @@ supported by `nitrokey-rs`: methods are only relevant for older firmware versions (pre-v0.51). As the Nitrokey Storage firmware can be updated easily, we do not support these outdated versions. +- `NK_totp_get_time`, `NK_status`. These functions are deprecated. +- `NK_read_HOTP_slot`. This function is only available for HOTP slots, not for + TOTP. We will support it once both types are supported by `libnitrokey`. +- All `*_as_string` functions that return string representations of data + returned by other functions. ## Tests @@ -82,7 +80,6 @@ under the [LGPL-3.0][]. [`libnitrokey`]: https://github.com/nitrokey/libnitrokey [`nitrokey-test`]: https://github.com/d-e-s-o/nitrokey-test [nitrokey-rs-dev@ireas.org]: mailto:nitrokey-rs-dev@ireas.org -[pull request #114]: https://github.com/Nitrokey/libnitrokey/pull/114 [MIT license]: https://opensource.org/licenses/MIT [LGPL-3.0]: https://opensource.org/licenses/lgpl-3.0.html [reuse]: https://reuse.software/practices/2.0/ diff --git a/nitrokey/TODO.md b/nitrokey/TODO.md index 1ff723d..efa66d3 100644 --- a/nitrokey/TODO.md +++ b/nitrokey/TODO.md @@ -6,10 +6,16 @@ SPDX-License-Identifier: MIT - Add support for the currently unsupported commands: - `NK_send_startup` - `NK_fill_SD_card_with_random_data` - - `NK_get_SD_usage_data_as_string` + - `NK_get_SD_usage_data` - `NK_get_progress_bar_value` - `NK_list_devices_by_cpuID` - `NK_connect_with_ID` + - `NK_get_status` + - `NK_list_devices` + - `NK_free_device_info` + - `NK_connect_with_path` + - `NK_enable_firmware_update_pro` + - `NK_change_firmware_password_pro` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). diff --git a/nitrokey/src/auth.rs b/nitrokey/src/auth.rs index f9f50fa..0b000f7 100644 --- a/nitrokey/src/auth.rs +++ b/nitrokey/src/auth.rs @@ -1,6 +1,7 @@ // Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org> // SPDX-License-Identifier: MIT +use std::marker; use std::ops; use std::os::raw::c_char; use std::os::raw::c_int; @@ -18,7 +19,7 @@ static TEMPORARY_PASSWORD_LENGTH: usize = 25; /// Provides methods to authenticate as a user or as an admin using a PIN. The authenticated /// methods will consume the current device instance. On success, they return the authenticated /// device. Otherwise, they return the current unauthenticated device and the error code. -pub trait Authenticate { +pub trait Authenticate<'a> { /// Performs user authentication. This method consumes the device. If successful, an /// authenticated device is returned. Otherwise, the current unauthenticated device and the /// error are returned. @@ -38,11 +39,12 @@ pub trait Authenticate { /// use nitrokey::{Authenticate, DeviceWrapper, User}; /// # use nitrokey::Error; /// - /// fn perform_user_task(device: &User<DeviceWrapper>) {} + /// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -61,9 +63,9 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> + fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> where - Self: Device + Sized; + Self: Device<'a> + Sized; /// Performs admin authentication. This method consumes the device. If successful, an /// authenticated device is returned. Otherwise, the current unauthenticated device and the @@ -84,11 +86,12 @@ pub trait Authenticate { /// use nitrokey::{Authenticate, Admin, DeviceWrapper}; /// # use nitrokey::Error; /// - /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {} + /// fn perform_admin_task<'a>(device: &Admin<'a, DeviceWrapper<'a>>) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let device = match device.authenticate_admin("123456") { /// Ok(admin) => { /// perform_admin_task(&admin); @@ -107,9 +110,9 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> + fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> where - Self: Device + Sized; + Self: Device<'a> + Sized; } trait AuthenticatedDevice<T> { @@ -128,9 +131,10 @@ trait AuthenticatedDevice<T> { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct User<T: Device> { +pub struct User<'a, T: Device<'a>> { device: T, temp_password: Vec<u8>, + marker: marker::PhantomData<&'a T>, } /// A Nitrokey device with admin authentication. @@ -143,14 +147,15 @@ pub struct User<T: Device> { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct Admin<T: Device> { +pub struct Admin<'a, T: Device<'a>> { device: T, temp_password: Vec<u8>, + marker: marker::PhantomData<&'a T>, } -fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)> +fn authenticate<'a, D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)> where - D: Device, + D: Device<'a>, A: AuthenticatedDevice<D>, T: Fn(*const c_char, *const c_char) -> c_int, { @@ -170,14 +175,14 @@ where } } -fn authenticate_user_wrapper<T, C>( +fn authenticate_user_wrapper<'a, T, C>( device: T, constructor: C, password: &str, -) -> Result<User<DeviceWrapper>, (DeviceWrapper, Error)> +) -> Result<User<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)> where - T: Device, - C: Fn(T) -> DeviceWrapper, + T: Device<'a> + 'a, + C: Fn(T) -> DeviceWrapper<'a>, { let result = device.authenticate_user(password); match result { @@ -186,14 +191,14 @@ where } } -fn authenticate_admin_wrapper<T, C>( +fn authenticate_admin_wrapper<'a, T, C>( device: T, constructor: C, password: &str, -) -> Result<Admin<DeviceWrapper>, (DeviceWrapper, Error)> +) -> Result<Admin<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)> where - T: Device, - C: Fn(T) -> DeviceWrapper, + T: Device<'a> + 'a, + C: Fn(T) -> DeviceWrapper<'a>, { let result = device.authenticate_admin(password); match result { @@ -202,7 +207,7 @@ where } } -impl<T: Device> User<T> { +impl<'a, T: Device<'a>> User<'a, T> { /// Forgets the user authentication and returns an unauthenticated device. This method /// consumes the authenticated device. It does not perform any actual commands on the /// Nitrokey. @@ -211,7 +216,7 @@ impl<T: Device> User<T> { } } -impl<T: Device> ops::Deref for User<T> { +impl<'a, T: Device<'a>> ops::Deref for User<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -219,13 +224,13 @@ impl<T: Device> ops::Deref for User<T> { } } -impl<T: Device> ops::DerefMut for User<T> { +impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> { fn deref_mut(&mut self) -> &mut T { &mut self.device } } -impl<T: Device> GenerateOtp for User<T> { +impl<'a, T: Device<'a>> GenerateOtp for User<'a, T> { fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> { result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) @@ -239,11 +244,12 @@ impl<T: Device> GenerateOtp for User<T> { } } -impl<T: Device> AuthenticatedDevice<T> for User<T> { +impl<'a, T: Device<'a>> AuthenticatedDevice<T> for User<'a, T> { fn new(device: T, temp_password: Vec<u8>) -> Self { User { device, temp_password, + marker: marker::PhantomData, } } @@ -252,7 +258,7 @@ impl<T: Device> AuthenticatedDevice<T> for User<T> { } } -impl<T: Device> ops::Deref for Admin<T> { +impl<'a, T: Device<'a>> ops::Deref for Admin<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -260,13 +266,13 @@ impl<T: Device> ops::Deref for Admin<T> { } } -impl<T: Device> ops::DerefMut for Admin<T> { +impl<'a, T: Device<'a>> ops::DerefMut for Admin<'a, T> { fn deref_mut(&mut self) -> &mut T { &mut self.device } } -impl<T: Device> Admin<T> { +impl<'a, T: Device<'a>> Admin<'a, T> { /// Forgets the user authentication and returns an unauthenticated device. This method /// consumes the authenticated device. It does not perform any actual commands on the /// Nitrokey. @@ -287,7 +293,8 @@ impl<T: Device> Admin<T> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let config = Config::new(None, None, None, false); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -316,7 +323,7 @@ impl<T: Device> Admin<T> { } } -impl<T: Device> ConfigureOtp for Admin<T> { +impl<'a, T: Device<'a>> ConfigureOtp for Admin<'a, T> { fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error> { let raw_data = RawOtpSlotData::new(data)?; get_command_result(unsafe { @@ -364,11 +371,12 @@ impl<T: Device> ConfigureOtp for Admin<T> { } } -impl<T: Device> AuthenticatedDevice<T> for Admin<T> { +impl<'a, T: Device<'a>> AuthenticatedDevice<T> for Admin<'a, T> { fn new(device: T, temp_password: Vec<u8>) -> Self { Admin { device, temp_password, + marker: marker::PhantomData, } } @@ -377,8 +385,8 @@ impl<T: Device> AuthenticatedDevice<T> for Admin<T> { } } -impl Authenticate for DeviceWrapper { - fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> { +impl<'a> Authenticate<'a> for DeviceWrapper<'a> { + fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) @@ -387,7 +395,7 @@ impl Authenticate for DeviceWrapper { } } - fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> { + fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) @@ -399,28 +407,28 @@ impl Authenticate for DeviceWrapper { } } -impl Authenticate for Pro { - fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> { +impl<'a> Authenticate<'a> for Pro<'a> { + fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) }) } - fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> { + fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) } } -impl Authenticate for Storage { - fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> { +impl<'a> Authenticate<'a> for Storage<'a> { + fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) }) } - fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> { + fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) diff --git a/nitrokey/src/device.rs b/nitrokey/src/device.rs index f6492cd..758d4c1 100644 --- a/nitrokey/src/device.rs +++ b/nitrokey/src/device.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT use std::fmt; -use std::marker; use libc; use nitrokey_sys; @@ -54,7 +53,7 @@ impl fmt::Display for VolumeMode { /// A wrapper for a Nitrokey device of unknown type. /// -/// Use the function [`connect`][] to obtain a wrapped instance. The wrapper implements all traits +/// Use the [`connect`][] method to obtain a wrapped instance. The wrapper implements all traits /// that are shared between all Nitrokey devices so that the shared functionality can be used /// without knowing the type of the underlying device. If you want to use functionality that is /// not available for all devices, you have to extract the device. @@ -67,11 +66,12 @@ impl fmt::Display for VolumeMode { /// use nitrokey::{Authenticate, DeviceWrapper, User}; /// # use nitrokey::Error; /// -/// fn perform_user_task(device: &User<DeviceWrapper>) {} +/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -97,7 +97,8 @@ impl fmt::Display for VolumeMode { /// fn perform_storage_task(device: &Storage) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; /// perform_common_task(&device); /// match device { /// DeviceWrapper::Storage(storage) => perform_storage_task(&storage), @@ -107,21 +108,20 @@ impl fmt::Display for VolumeMode { /// # } /// ``` /// -/// [`connect`]: fn.connect.html +/// [`connect`]: struct.Manager.html#method.connect #[derive(Debug)] -pub enum DeviceWrapper { +pub enum DeviceWrapper<'a> { /// A Nitrokey Storage device. - Storage(Storage), + Storage(Storage<'a>), /// A Nitrokey Pro device. - Pro(Pro), + Pro(Pro<'a>), } /// A Nitrokey Pro device without user or admin authentication. /// -/// Use the global function [`connect`][] to obtain an instance wrapper or the method -/// [`connect`][`Pro::connect`] to directly obtain an instance. If you want to execute a command -/// that requires user or admin authentication, use [`authenticate_admin`][] or -/// [`authenticate_user`][]. +/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to +/// directly obtain an instance. If you want to execute a command that requires user or admin +/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. /// /// # Examples /// @@ -131,11 +131,12 @@ pub enum DeviceWrapper { /// use nitrokey::{Authenticate, User, Pro}; /// # use nitrokey::Error; /// -/// fn perform_user_task(device: &User<Pro>) {} +/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {} /// fn perform_other_task(device: &Pro) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::Pro::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_pro()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -153,21 +154,18 @@ pub enum DeviceWrapper { /// /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -/// [`connect`]: fn.connect.html -/// [`Pro::connect`]: #method.connect +/// [`connect`]: struct.Manager.html#method.connect +/// [`connect_pro`]: struct.Manager.html#method.connect_pro #[derive(Debug)] -pub struct Pro { - // make sure that users cannot directly instantiate this type - #[doc(hidden)] - marker: marker::PhantomData<()>, +pub struct Pro<'a> { + manager: Option<&'a mut crate::Manager>, } /// A Nitrokey Storage device without user or admin authentication. /// -/// Use the global function [`connect`][] to obtain an instance wrapper or the method -/// [`connect`][`Storage::connect`] to directly obtain an instance. If you want to execute a -/// command that requires user or admin authentication, use [`authenticate_admin`][] or -/// [`authenticate_user`][]. +/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_storage`] method to +/// directly obtain an instance. If you want to execute a command that requires user or admin +/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. /// /// # Examples /// @@ -177,11 +175,12 @@ pub struct Pro { /// use nitrokey::{Authenticate, User, Storage}; /// # use nitrokey::Error; /// -/// fn perform_user_task(device: &User<Storage>) {} +/// fn perform_user_task<'a>(device: &User<'a, Storage<'a>>) {} /// fn perform_other_task(device: &Storage) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::Storage::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_storage()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -199,13 +198,11 @@ pub struct Pro { /// /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -/// [`connect`]: fn.connect.html -/// [`Storage::connect`]: #method.connect +/// [`connect`]: struct.Manager.html#method.connect +/// [`connect_storage`]: struct.Manager.html#method.connect_storage #[derive(Debug)] -pub struct Storage { - // make sure that users cannot directly instantiate this type - #[doc(hidden)] - marker: marker::PhantomData<()>, +pub struct Storage<'a> { + manager: Option<&'a mut crate::Manager>, } /// The status of a volume on a Nitrokey Storage device. @@ -296,7 +293,32 @@ pub struct StorageStatus { /// /// This trait provides the commands that can be executed without authentication and that are /// present on all supported Nitrokey devices. -pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { +pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug { + /// Returns the [`Manager`][] instance that has been used to connect to this device. + /// + /// # Example + /// + /// ``` + /// use nitrokey::{Device, DeviceWrapper}; + /// + /// fn do_something(device: DeviceWrapper) { + /// // reconnect to any device + /// let manager = device.into_manager(); + /// let device = manager.connect(); + /// // do something with the device + /// // ... + /// } + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect() { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + fn into_manager(self) -> &'a mut crate::Manager; + /// Returns the model of the connected Nitrokey device. /// /// # Example @@ -306,7 +328,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// println!("Connected to a Nitrokey {}", device.get_model()); /// # Ok(()) /// # } @@ -322,7 +345,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_serial_number() { /// Ok(number) => println!("serial no: {}", number), /// Err(err) => eprintln!("Could not get serial number: {}", err), @@ -344,7 +368,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// let count = device.get_user_retry_count(); /// match device.get_user_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (user)", count), /// Err(err) => eprintln!("Could not get user retry count: {}", err), @@ -366,7 +392,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let count = device.get_admin_retry_count(); /// match device.get_admin_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), @@ -388,7 +415,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_firmware_version() { /// Ok(version) => println!("Firmware version: {}", version), /// Err(err) => eprintln!("Could not access firmware version: {}", err), @@ -399,14 +427,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { fn get_firmware_version(&self) -> Result<FirmwareVersion, Error> { let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?; let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?; - let max = i32::from(u8::max_value()); - if major < 0 || minor < 0 || major > max || minor > max { - return Err(Error::UnexpectedError); - } - Ok(FirmwareVersion { - major: major as u8, - minor: minor as u8, - }) + Ok(FirmwareVersion { major, minor }) } /// Returns the current configuration of the Nitrokey device. @@ -418,7 +439,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let config = device.get_config()?; /// println!("numlock binding: {:?}", config.numlock); /// println!("capslock binding: {:?}", config.capslock); @@ -452,7 +474,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.change_admin_pin("12345678", "12345679") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), @@ -485,7 +508,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.change_user_pin("123456", "123457") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), @@ -518,7 +542,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.unlock_user_pin("12345678", "123456") { /// Ok(()) => println!("Unlocked user PIN."), /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), @@ -552,7 +577,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.lock() { /// Ok(()) => println!("Locked the Nitrokey device."), /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), @@ -583,7 +609,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.factory_reset("12345678") { /// Ok(()) => println!("Performed a factory reset."), /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), @@ -617,7 +644,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.build_aes_key("12345678") { /// Ok(()) => println!("New AES keys have been built."), /// Err(err) => eprintln!("Could not build new AES keys: {}", err), @@ -633,67 +661,6 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { } } -/// Connects to a Nitrokey device. This method can be used to connect to any connected device, -/// both a Nitrokey Pro and a Nitrokey Storage. -/// -/// # Errors -/// -/// - [`NotConnected`][] if no Nitrokey device is connected -/// -/// # Example -/// -/// ``` -/// use nitrokey::DeviceWrapper; -/// -/// fn do_something(device: DeviceWrapper) {} -/// -/// match nitrokey::connect() { -/// Ok(device) => do_something(device), -/// Err(err) => eprintln!("Could not connect to a Nitrokey: {}", err), -/// } -/// ``` -/// -/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected -pub fn connect() -> Result<DeviceWrapper, Error> { - if unsafe { nitrokey_sys::NK_login_auto() } == 1 { - match get_connected_device() { - Some(wrapper) => Ok(wrapper), - None => Err(CommunicationError::NotConnected.into()), - } - } else { - Err(CommunicationError::NotConnected.into()) - } -} - -/// Connects to a Nitrokey device of the given model. -/// -/// # Errors -/// -/// - [`NotConnected`][] if no Nitrokey device of the given model is connected -/// -/// # Example -/// -/// ``` -/// use nitrokey::DeviceWrapper; -/// use nitrokey::Model; -/// -/// fn do_something(device: DeviceWrapper) {} -/// -/// match nitrokey::connect_model(Model::Pro) { -/// Ok(device) => do_something(device), -/// Err(err) => eprintln!("Could not connect to a Nitrokey Pro: {}", err), -/// } -/// ``` -/// -/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected -pub fn connect_model(model: Model) -> Result<DeviceWrapper, Error> { - if connect_enum(model) { - Ok(create_device_wrapper(model)) - } else { - Err(CommunicationError::NotConnected.into()) - } -} - fn get_connected_model() -> Option<Model> { match unsafe { nitrokey_sys::NK_get_device_model() } { nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), @@ -702,18 +669,26 @@ fn get_connected_model() -> Option<Model> { } } -fn create_device_wrapper(model: Model) -> DeviceWrapper { +pub(crate) fn create_device_wrapper( + manager: &mut crate::Manager, + model: Model, +) -> DeviceWrapper<'_> { match model { - Model::Pro => Pro::new().into(), - Model::Storage => Storage::new().into(), + Model::Pro => Pro::new(manager).into(), + Model::Storage => Storage::new(manager).into(), } } -fn get_connected_device() -> Option<DeviceWrapper> { - get_connected_model().map(create_device_wrapper) +pub(crate) fn get_connected_device( + manager: &mut crate::Manager, +) -> Result<DeviceWrapper<'_>, Error> { + match get_connected_model() { + Some(model) => Ok(create_device_wrapper(manager, model)), + None => Err(CommunicationError::NotConnected.into()), + } } -fn connect_enum(model: Model) -> bool { +pub(crate) fn connect_enum(model: Model) -> bool { let model = match model { Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, @@ -721,15 +696,15 @@ fn connect_enum(model: Model) -> bool { unsafe { nitrokey_sys::NK_login_enum(model) == 1 } } -impl DeviceWrapper { - fn device(&self) -> &dyn Device { +impl<'a> DeviceWrapper<'a> { + fn device(&self) -> &dyn Device<'a> { match *self { DeviceWrapper::Storage(ref storage) => storage, DeviceWrapper::Pro(ref pro) => pro, } } - fn device_mut(&mut self) -> &mut dyn Device { + fn device_mut(&mut self) -> &mut dyn Device<'a> { match *self { DeviceWrapper::Storage(ref mut storage) => storage, DeviceWrapper::Pro(ref mut pro) => pro, @@ -737,19 +712,19 @@ impl DeviceWrapper { } } -impl From<Pro> for DeviceWrapper { - fn from(device: Pro) -> Self { +impl<'a> From<Pro<'a>> for DeviceWrapper<'a> { + fn from(device: Pro<'a>) -> Self { DeviceWrapper::Pro(device) } } -impl From<Storage> for DeviceWrapper { - fn from(device: Storage) -> Self { +impl<'a> From<Storage<'a>> for DeviceWrapper<'a> { + fn from(device: Storage<'a>) -> Self { DeviceWrapper::Storage(device) } } -impl GenerateOtp for DeviceWrapper { +impl<'a> GenerateOtp for DeviceWrapper<'a> { fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> { self.device().get_hotp_slot_name(slot) } @@ -767,7 +742,14 @@ impl GenerateOtp for DeviceWrapper { } } -impl Device for DeviceWrapper { +impl<'a> Device<'a> for DeviceWrapper<'a> { + fn into_manager(self) -> &'a mut crate::Manager { + match self { + DeviceWrapper::Pro(dev) => dev.into_manager(), + DeviceWrapper::Storage(dev) => dev.into_manager(), + } + } + fn get_model(&self) -> Model { match *self { DeviceWrapper::Pro(_) => Model::Pro, @@ -776,44 +758,15 @@ impl Device for DeviceWrapper { } } -impl Pro { - /// Connects to a Nitrokey Pro. - /// - /// # Errors - /// - /// - [`NotConnected`][] if no Nitrokey device of the given model is connected - /// - /// # Example - /// - /// ``` - /// use nitrokey::Pro; - /// - /// fn use_pro(device: Pro) {} - /// - /// match nitrokey::Pro::connect() { - /// Ok(device) => use_pro(device), - /// Err(err) => eprintln!("Could not connect to the Nitrokey Pro: {}", err), - /// } - /// ``` - /// - /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - pub fn connect() -> Result<Pro, Error> { - // TODO: maybe Option instead of Result? - if connect_enum(Model::Pro) { - Ok(Pro::new()) - } else { - Err(CommunicationError::NotConnected.into()) - } - } - - fn new() -> Pro { +impl<'a> Pro<'a> { + pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> { Pro { - marker: marker::PhantomData, + manager: Some(manager), } } } -impl Drop for Pro { +impl<'a> Drop for Pro<'a> { fn drop(&mut self) { unsafe { nitrokey_sys::NK_logout(); @@ -821,47 +774,22 @@ impl Drop for Pro { } } -impl Device for Pro { +impl<'a> Device<'a> for Pro<'a> { + fn into_manager(mut self) -> &'a mut crate::Manager { + self.manager.take().unwrap() + } + fn get_model(&self) -> Model { Model::Pro } } -impl GenerateOtp for Pro {} +impl<'a> GenerateOtp for Pro<'a> {} -impl Storage { - /// Connects to a Nitrokey Storage. - /// - /// # Errors - /// - /// - [`NotConnected`][] if no Nitrokey device of the given model is connected - /// - /// # Example - /// - /// ``` - /// use nitrokey::Storage; - /// - /// fn use_storage(device: Storage) {} - /// - /// match nitrokey::Storage::connect() { - /// Ok(device) => use_storage(device), - /// Err(err) => eprintln!("Could not connect to the Nitrokey Storage: {}", err), - /// } - /// ``` - /// - /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - pub fn connect() -> Result<Storage, Error> { - // TODO: maybe Option instead of Result? - if connect_enum(Model::Storage) { - Ok(Storage::new()) - } else { - Err(CommunicationError::NotConnected.into()) - } - } - - fn new() -> Storage { +impl<'a> Storage<'a> { + pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> { Storage { - marker: marker::PhantomData, + manager: Some(manager), } } @@ -882,7 +810,8 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.change_update_pin("12345678", "87654321") { /// Ok(()) => println!("Updated update PIN."), /// Err(err) => eprintln!("Failed to update update PIN: {}", err), @@ -919,7 +848,8 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.enable_firmware_update("12345678") { /// Ok(()) => println!("Nitrokey entered update mode."), /// Err(err) => eprintln!("Could not enter update mode: {}", err), @@ -953,7 +883,8 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => println!("Enabled the encrypted volume."), /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), @@ -982,7 +913,8 @@ impl Storage { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => { /// println!("Enabled the encrypted volume."); @@ -1028,7 +960,8 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => println!("Enabled a hidden volume."), @@ -1061,7 +994,8 @@ impl Storage { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => { @@ -1108,7 +1042,8 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// device.enable_encrypted_volume("123445")?; /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?; /// # Ok(()) @@ -1148,7 +1083,8 @@ impl Storage { /// use nitrokey::VolumeMode; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), @@ -1193,7 +1129,8 @@ impl Storage { /// use nitrokey::VolumeMode; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the encrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err), @@ -1231,7 +1168,8 @@ impl Storage { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect_storage()?; /// match device.get_status() { /// Ok(status) => { /// println!("SD card ID: {:#x}", status.serial_number_sd_card); @@ -1274,7 +1212,8 @@ impl Storage { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect_storage()?; /// match device.get_production_info() { /// Ok(data) => { /// println!("SD card ID: {:#x}", data.sd_card.serial_number); @@ -1322,7 +1261,8 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.clear_new_sd_card_warning("12345678") { /// Ok(()) => println!("Cleared the new SD card warning."), /// Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err), @@ -1367,7 +1307,7 @@ impl Storage { } } -impl Drop for Storage { +impl<'a> Drop for Storage<'a> { fn drop(&mut self) { unsafe { nitrokey_sys::NK_logout(); @@ -1375,13 +1315,17 @@ impl Drop for Storage { } } -impl Device for Storage { +impl<'a> Device<'a> for Storage<'a> { + fn into_manager(mut self) -> &'a mut crate::Manager { + self.manager.take().unwrap() + } + fn get_model(&self) -> Model { Model::Storage } } -impl GenerateOtp for Storage {} +impl<'a> GenerateOtp for Storage<'a> {} impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo { fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self { diff --git a/nitrokey/src/error.rs b/nitrokey/src/error.rs index 1730171..9e6adc0 100644 --- a/nitrokey/src/error.rs +++ b/nitrokey/src/error.rs @@ -5,6 +5,7 @@ use std::error; use std::fmt; use std::os::raw; use std::str; +use std::sync; use crate::device; @@ -13,11 +14,15 @@ use crate::device; pub enum Error { /// An error reported by the Nitrokey device in the response packet. CommandError(CommandError), - /// A device communication. + /// A device communication error. CommunicationError(CommunicationError), + /// An error occurred due to concurrent access to the Nitrokey device. + ConcurrentAccessError, /// A library usage error. LibraryError(LibraryError), - /// An error that occured during random number generation. + /// An error that occurred due to a poisoned lock. + PoisonError(sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>), + /// An error that occurred during random number generation. RandError(Box<dyn error::Error>), /// An error that is caused by an unexpected value returned by libnitrokey. UnexpectedError, @@ -65,7 +70,22 @@ impl From<str::Utf8Error> for Error { } } -impl<T: device::Device> From<(T, Error)> for Error { +impl From<sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>> for Error { + fn from(error: sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>) -> Self { + Error::PoisonError(error) + } +} + +impl From<sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>> for Error { + fn from(error: sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>) -> Self { + match error { + sync::TryLockError::Poisoned(err) => err.into(), + sync::TryLockError::WouldBlock => Error::ConcurrentAccessError, + } + } +} + +impl<'a, T: device::Device<'a>> From<(T, Error)> for Error { fn from((_, err): (T, Error)) -> Self { err } @@ -76,7 +96,9 @@ impl error::Error for Error { match *self { Error::CommandError(ref err) => Some(err), Error::CommunicationError(ref err) => Some(err), + Error::ConcurrentAccessError => None, Error::LibraryError(ref err) => Some(err), + Error::PoisonError(ref err) => Some(err), Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, Error::UnknownError(_) => None, @@ -90,7 +112,9 @@ impl fmt::Display for Error { match *self { Error::CommandError(ref err) => write!(f, "Command error: {}", err), Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err), + Error::ConcurrentAccessError => write!(f, "Internal error: concurrent access"), Error::LibraryError(ref err) => write!(f, "Library error: {}", err), + Error::PoisonError(_) => write!(f, "Internal error: poisoned lock"), Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::UnexpectedError => write!(f, "An unexpected error occurred"), Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err), diff --git a/nitrokey/src/lib.rs b/nitrokey/src/lib.rs index c35829c..a4402c5 100644 --- a/nitrokey/src/lib.rs +++ b/nitrokey/src/lib.rs @@ -9,13 +9,16 @@ //! performed without authentication, some require user access, and some require admin access. //! This is modelled using the types [`User`][] and [`Admin`][]. //! -//! Use [`connect`][] to connect to any Nitrokey device. The method will return a +//! You can only connect to one Nitrokey at a time. Use the global [`take`][] function to obtain +//! an reference to the [`Manager`][] singleton that keeps track of the connections. Then use the +//! [`connect`][] method to connect to any Nitrokey device. The method will return a //! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices. You can also use -//! [`Pro::connect`][] or [`Storage::connect`][] to connect to a specific device. +//! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific +//! device. //! -//! You can then use [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated -//! device that can perform operations that require authentication. You can use [`device`][] to go -//! back to the unauthenticated device. +//! You can call [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated device +//! that can perform operations that require authentication. You can use [`device`][] to go back +//! to the unauthenticated device. //! //! This makes sure that you can only execute a command if you have the required access rights. //! Otherwise, your code will not compile. The only exception are the methods to generate one-time @@ -31,7 +34,8 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let device = nitrokey::connect()?; +//! let mut manager = nitrokey::take()?; +//! let device = manager.connect()?; //! println!("{}", device.get_serial_number()?); //! # Ok(()) //! # } @@ -44,7 +48,8 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let device = nitrokey::connect()?; +//! let mut manager = nitrokey::take()?; +//! let device = manager.connect()?; //! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); //! match device.authenticate_admin("12345678") { //! Ok(mut admin) => { @@ -66,7 +71,8 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let mut device = nitrokey::connect()?; +//! let mut manager = nitrokey::take()?; +//! let mut device = manager.connect()?; //! match device.get_hotp_code(1) { //! Ok(code) => println!("Generated HOTP code: {}", code), //! Err(err) => eprintln!("Could not generate HOTP code: {}", err), @@ -77,9 +83,12 @@ //! //! [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin //! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -//! [`connect`]: fn.connect.html -//! [`Pro::connect`]: struct.Pro.html#fn.connect.html -//! [`Storage::connect`]: struct.Storage.html#fn.connect.html +//! [`take`]: fn.take.html +//! [`connect`]: struct.Manager.html#method.connect +//! [`connect_model`]: struct.Manager.html#method.connect_model +//! [`connect_pro`]: struct.Manager.html#method.connect_pro +//! [`connect_storage`]: struct.Manager.html#method.connect_storage +//! [`manager`]: trait.Device.html#method.manager //! [`device`]: struct.User.html#method.device //! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code //! [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code @@ -89,6 +98,9 @@ #![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)] +#[macro_use(lazy_static)] +extern crate lazy_static; + mod auth; mod config; mod device; @@ -98,14 +110,16 @@ mod pws; mod util; use std::fmt; +use std::marker; +use std::sync; use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; pub use crate::device::{ - connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, - StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, + Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, + VolumeMode, VolumeStatus, }; pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; @@ -117,6 +131,10 @@ pub const DEFAULT_ADMIN_PIN: &str = "12345678"; /// The default user PIN for all Nitrokey devices. pub const DEFAULT_USER_PIN: &str = "123456"; +lazy_static! { + static ref MANAGER: sync::Mutex<Manager> = sync::Mutex::new(Manager::new()); +} + /// A version of the libnitrokey library. /// /// Use the [`get_library_version`](fn.get_library_version.html) function to query the library @@ -147,6 +165,263 @@ impl fmt::Display for Version { } } +/// A manager for connections to Nitrokey devices. +/// +/// Currently, libnitrokey only provides access to one Nitrokey device at the same time. This +/// manager struct makes sure that `nitrokey-rs` does not try to connect to two devices at the same +/// time. +/// +/// To obtain a reference to an instance of this manager, use the [`take`][] function. Use one of +/// the connect methods – [`connect`][], [`connect_model`][], [`connect_pro`][] or +/// [`connect_storage`][] – to retrieve a [`Device`][] instance. +/// +/// # Examples +/// +/// Connect to a single device: +/// +/// ```no_run +/// use nitrokey::Device; +/// # use nitrokey::Error; +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; +/// println!("{}", device.get_serial_number()?); +/// # Ok(()) +/// # } +/// ``` +/// +/// Connect to a Pro and a Storage device: +/// +/// ```no_run +/// use nitrokey::{Device, Model}; +/// # use nitrokey::Error; +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_model(Model::Pro)?; +/// println!("Pro: {}", device.get_serial_number()?); +/// drop(device); +/// let device = manager.connect_model(Model::Storage)?; +/// println!("Storage: {}", device.get_serial_number()?); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`connect`]: #method.connect +/// [`connect_model`]: #method.connect_model +/// [`connect_pro`]: #method.connect_pro +/// [`connect_storage`]: #method.connect_storage +/// [`manager`]: trait.Device.html#method.manager +/// [`take`]: fn.take.html +/// [`Device`]: trait.Device.html +#[derive(Debug)] +pub struct Manager { + marker: marker::PhantomData<()>, +} + +impl Manager { + fn new() -> Self { + Manager { + marker: marker::PhantomData, + } + } + + /// Connects to a Nitrokey device. + /// + /// This method can be used to connect to any connected device, both a Nitrokey Pro and a + /// Nitrokey Storage. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::DeviceWrapper; + /// + /// fn do_something(device: DeviceWrapper) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// let mut manager = nitrokey::take()?; + /// match manager.connect() { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect(&mut self) -> Result<DeviceWrapper<'_>, Error> { + if unsafe { nitrokey_sys::NK_login_auto() } == 1 { + device::get_connected_device(self) + } else { + Err(CommunicationError::NotConnected.into()) + } + } + + /// Connects to a Nitrokey device of the given model. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::DeviceWrapper; + /// use nitrokey::Model; + /// + /// fn do_something(device: DeviceWrapper) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect_model(Model::Pro) { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect_model(&mut self, model: Model) -> Result<DeviceWrapper<'_>, Error> { + if device::connect_enum(model) { + Ok(device::create_device_wrapper(self, model)) + } else { + Err(CommunicationError::NotConnected.into()) + } + } + + /// Connects to a Nitrokey Pro. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::Pro; + /// + /// fn use_pro(device: Pro) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect_pro() { + /// Ok(device) => use_pro(device), + /// Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect_pro(&mut self) -> Result<Pro<'_>, Error> { + if device::connect_enum(device::Model::Pro) { + Ok(device::Pro::new(self)) + } else { + Err(CommunicationError::NotConnected.into()) + } + } + + /// Connects to a Nitrokey Storage. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::Storage; + /// + /// fn use_storage(device: Storage) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect_storage() { + /// Ok(device) => use_storage(device), + /// Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect_storage(&mut self) -> Result<Storage<'_>, Error> { + if device::connect_enum(Model::Storage) { + Ok(Storage::new(self)) + } else { + Err(CommunicationError::NotConnected.into()) + } + } +} + +/// Take an instance of the connection manager, blocking until an instance is available. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, this method blocks. If you want a non-blocking version, use [`take`][]. +/// +/// # Errors +/// +/// - [`PoisonError`][] if the lock is poisoned +/// +/// [`take`]: fn.take.html +/// [`PoisonError`]: struct.Error.html#variant.PoisonError +/// [`Manager`]: struct.Manager.html +pub fn take_blocking() -> Result<sync::MutexGuard<'static, Manager>, Error> { + MANAGER.lock().map_err(Into::into) +} + +/// Try to take an instance of the connection manager. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use +/// [`take_blocking`][]. If you want to access the manager instance even if the cache is poisoned, +/// use [`force_take`][]. +/// +/// # Errors +/// +/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked +/// - [`PoisonError`][] if the lock is poisoned +/// +/// [`take_blocking`]: fn.take_blocking.html +/// [`force_take`]: fn.force_take.html +/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError +/// [`PoisonError`]: struct.Error.html#variant.PoisonError +/// [`Manager`]: struct.Manager.html +pub fn take() -> Result<sync::MutexGuard<'static, Manager>, Error> { + MANAGER.try_lock().map_err(Into::into) +} + +/// Try to take an instance of the connection manager, ignoring a poisoned cache. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use +/// [`take_blocking`][]. +/// +/// If a thread has previously panicked while accessing the manager instance, the cache is +/// poisoned. The default implementation, [`take`][], returns a [`PoisonError`][] on subsequent +/// calls. This implementation ignores the poisoned cache and returns the manager instance. +/// +/// # Errors +/// +/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked +/// +/// [`take`]: fn.take.html +/// [`take_blocking`]: fn.take_blocking.html +/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError +/// [`Manager`]: struct.Manager.html +pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> { + match take() { + Ok(guard) => Ok(guard), + Err(err) => match err { + Error::PoisonError(err) => Ok(err.into_inner()), + err => Err(err), + }, + } +} + /// Enables or disables debug output. Calling this method with `true` is equivalent to setting the /// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see /// [`set_log_level`][]). diff --git a/nitrokey/src/otp.rs b/nitrokey/src/otp.rs index ee142c7..4667aff 100644 --- a/nitrokey/src/otp.rs +++ b/nitrokey/src/otp.rs @@ -35,7 +35,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -71,7 +72,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -104,7 +106,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_hotp_slot(1) { @@ -134,7 +137,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_totp_slot(1) { @@ -171,7 +175,8 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => device.set_time(time.as_secs(), false)?, @@ -209,7 +214,8 @@ pub trait GenerateOtp { /// use nitrokey::{CommandError, Error, GenerateOtp}; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_hotp_slot_name(1) { /// Ok(name) => println!("HOTP slot 1: {}", name), /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("HOTP slot 1 not programmed"), @@ -238,7 +244,8 @@ pub trait GenerateOtp { /// use nitrokey::{CommandError, Error, GenerateOtp}; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_totp_slot_name(1) { /// Ok(name) => println!("TOTP slot 1: {}", name), /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("TOTP slot 1 not programmed"), @@ -270,7 +277,8 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let code = device.get_hotp_code(1)?; /// println!("Generated HOTP code on slot 1: {}", code); /// # Ok(()) @@ -305,7 +313,8 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => { diff --git a/nitrokey/src/pws.rs b/nitrokey/src/pws.rs index 371de6e..3398deb 100644 --- a/nitrokey/src/pws.rs +++ b/nitrokey/src/pws.rs @@ -43,7 +43,8 @@ pub const SLOT_COUNT: u8 = 16; /// } /// /// # fn try_main() -> Result<(), Error> { -/// let mut device = nitrokey::connect()?; +/// let mut manager = nitrokey::take()?; +/// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// use_password_safe(&pws); /// drop(pws); @@ -57,8 +58,8 @@ pub const SLOT_COUNT: u8 = 16; /// [`lock`]: trait.Device.html#method.lock /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html #[derive(Debug)] -pub struct PasswordSafe<'a> { - _device: &'a dyn Device, +pub struct PasswordSafe<'a, 'b> { + _device: &'a dyn Device<'b>, } /// Provides access to a [`PasswordSafe`][]. @@ -67,7 +68,7 @@ pub struct PasswordSafe<'a> { /// retrieved from it. /// /// [`PasswordSafe`]: struct.PasswordSafe.html -pub trait GetPasswordSafe { +pub trait GetPasswordSafe<'a> { /// Enables and returns the password safe. /// /// The underlying device must always live at least as long as a password safe retrieved from @@ -98,7 +99,8 @@ pub trait GetPasswordSafe { /// fn use_password_safe(pws: &PasswordSafe) {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// use_password_safe(&pws); @@ -117,13 +119,13 @@ pub trait GetPasswordSafe { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error>; + fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error>; } -fn get_password_safe<'a>( - device: &'a dyn Device, +fn get_password_safe<'a, 'b>( + device: &'a dyn Device<'b>, user_pin: &str, -) -> Result<PasswordSafe<'a>, Error> { +) -> Result<PasswordSafe<'a, 'b>, Error> { let user_pin_string = get_cstring(user_pin)?; get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) }) .map(|_| PasswordSafe { _device: device }) @@ -137,7 +139,7 @@ fn get_pws_result(s: String) -> Result<String, Error> { } } -impl<'a> PasswordSafe<'a> { +impl<'a, 'b> PasswordSafe<'a, 'b> { /// Returns the status of all password slots. /// /// The status indicates whether a slot is programmed or not. @@ -149,7 +151,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { /// let status = match *programmed { @@ -194,7 +197,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; @@ -231,7 +235,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -264,7 +269,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -295,7 +301,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -341,7 +348,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let mut pws = device.get_password_safe("123456")?; /// match pws.erase_slot(0) { /// Ok(()) => println!("Erased slot 0."), @@ -357,27 +365,27 @@ impl<'a> PasswordSafe<'a> { } } -impl<'a> Drop for PasswordSafe<'a> { +impl<'a, 'b> Drop for PasswordSafe<'a, 'b> { fn drop(&mut self) { // TODO: disable the password safe -- NK_lock_device has side effects on the Nitrokey // Storage, see https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 } } -impl GetPasswordSafe for Pro { - fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> { +impl<'a> GetPasswordSafe<'a> for Pro<'a> { + fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> { get_password_safe(self, user_pin) } } -impl GetPasswordSafe for Storage { - fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> { +impl<'a> GetPasswordSafe<'a> for Storage<'a> { + fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> { get_password_safe(self, user_pin) } } -impl GetPasswordSafe for DeviceWrapper { - fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> { +impl<'a> GetPasswordSafe<'a> for DeviceWrapper<'a> { + fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> { get_password_safe(self, user_pin) } } diff --git a/nitrokey/tests/device.rs b/nitrokey/tests/device.rs index 5c52024..e367558 100644 --- a/nitrokey/tests/device.rs +++ b/nitrokey/tests/device.rs @@ -33,40 +33,39 @@ fn count_nitrokey_block_devices() -> usize { #[test_device] fn connect_no_device() { - assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect()); - assert_cmu_err!( - CommunicationError::NotConnected, - nitrokey::connect_model(nitrokey::Model::Pro) - ); + let mut manager = unwrap_ok!(nitrokey::take()); + + assert_cmu_err!(CommunicationError::NotConnected, manager.connect()); assert_cmu_err!( CommunicationError::NotConnected, - nitrokey::connect_model(nitrokey::Model::Storage) + manager.connect_model(nitrokey::Model::Pro) ); - assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Pro::connect()); assert_cmu_err!( CommunicationError::NotConnected, - nitrokey::Storage::connect() + manager.connect_model(nitrokey::Model::Storage) ); + assert_cmu_err!(CommunicationError::NotConnected, manager.connect_pro()); + assert_cmu_err!(CommunicationError::NotConnected, manager.connect_storage()); } #[test_device] fn connect_pro(device: Pro) { assert_eq!(device.get_model(), nitrokey::Model::Pro); - drop(device); - assert_any_ok!(nitrokey::connect()); - assert_any_ok!(nitrokey::connect_model(nitrokey::Model::Pro)); - assert_any_ok!(nitrokey::Pro::connect()); + let manager = device.into_manager(); + assert_any_ok!(manager.connect()); + assert_any_ok!(manager.connect_model(nitrokey::Model::Pro)); + assert_any_ok!(manager.connect_pro()); } #[test_device] fn connect_storage(device: Storage) { assert_eq!(device.get_model(), nitrokey::Model::Storage); - drop(device); - assert_any_ok!(nitrokey::connect()); - assert_any_ok!(nitrokey::connect_model(nitrokey::Model::Storage)); - assert_any_ok!(nitrokey::Storage::connect()); + let manager = device.into_manager(); + assert_any_ok!(manager.connect()); + assert_any_ok!(manager.connect_model(nitrokey::Model::Storage)); + assert_any_ok!(manager.connect_storage()); } fn assert_empty_serial_number() { @@ -97,7 +96,10 @@ fn get_firmware_version(device: Pro) { assert!(version.minor > 0); } -fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T { +fn admin_retry<'a, T>(device: T, suffix: &str, count: u8) -> T +where + T: Authenticate<'a> + Device<'a> + 'a, +{ let result = device.authenticate_admin(&(DEFAULT_ADMIN_PIN.to_owned() + suffix)); let device = match result { Ok(admin) => admin.device(), @@ -107,7 +109,10 @@ fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> return device; } -fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T { +fn user_retry<'a, T>(device: T, suffix: &str, count: u8) -> T +where + T: Authenticate<'a> + Device<'a> + 'a, +{ let result = device.authenticate_user(&(DEFAULT_USER_PIN.to_owned() + suffix)); let device = match result { Ok(admin) => admin.device(), @@ -216,10 +221,10 @@ fn change_admin_pin(device: DeviceWrapper) { device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); } -fn require_failed_user_login<D>(device: D, password: &str, error: CommandError) -> D +fn require_failed_user_login<'a, D>(device: D, password: &str, error: CommandError) -> D where - D: Device + Authenticate, - nitrokey::User<D>: std::fmt::Debug, + D: Device<'a> + Authenticate<'a> + 'a, + nitrokey::User<'a, D>: std::fmt::Debug, { let result = device.authenticate_user(password); assert!(result.is_err()); @@ -339,6 +344,7 @@ fn factory_reset(device: DeviceWrapper) { assert_utf8_err_or_ne("testpw", pws.get_slot_password(0)); drop(pws); + assert_ok!(3, device.get_user_retry_count()); assert_ok!((), device.build_aes_key(DEFAULT_ADMIN_PIN)); } diff --git a/nitrokey/tests/lib.rs b/nitrokey/tests/lib.rs index 8ab75f6..25aae0f 100644 --- a/nitrokey/tests/lib.rs +++ b/nitrokey/tests/lib.rs @@ -10,3 +10,19 @@ fn get_library_version() { assert!(version.git.is_empty() || version.git.starts_with("v")); assert!(version.major > 0); } + +#[test] +fn take_manager() { + assert!(nitrokey::take().is_ok()); + + let result = nitrokey::take(); + assert!(result.is_ok()); + let result2 = nitrokey::take(); + match result2 { + Ok(_) => panic!("Expected error, got Ok(_)!"), + Err(nitrokey::Error::ConcurrentAccessError) => {} + Err(err) => panic!("Expected ConcurrentAccessError, got {}", err), + } + drop(result); + assert!(nitrokey::take().is_ok()); +} diff --git a/nitrokey/tests/otp.rs b/nitrokey/tests/otp.rs index c0bbecf..aafda59 100644 --- a/nitrokey/tests/otp.rs +++ b/nitrokey/tests/otp.rs @@ -36,9 +36,9 @@ enum TotpTimestampSize { U64, } -fn make_admin_test_device<T>(device: T) -> Admin<T> +fn make_admin_test_device<'a, T>(device: T) -> Admin<'a, T> where - T: Device, + T: Device<'a>, (T, nitrokey::Error): Debug, { unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)) diff --git a/nitrokey/tests/pws.rs b/nitrokey/tests/pws.rs index b0e5abe..7169695 100644 --- a/nitrokey/tests/pws.rs +++ b/nitrokey/tests/pws.rs @@ -32,9 +32,9 @@ fn get_slot_name_direct(slot: u8) -> Result<String, Error> { } } -fn get_pws<T>(device: &mut T) -> PasswordSafe +fn get_pws<'a, T>(device: &mut T) -> PasswordSafe<'_, 'a> where - T: Device, + T: Device<'a>, { unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN)) } |