diff options
98 files changed, 20531 insertions, 0 deletions
diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index 70df662..bfe1ffa 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,3 +1,9 @@ +Unreleased +---------- +- Added `nitrokey` version `0.2.1` as a direct dependency and `nitrokey-sys` + version `3.4.1` as well as `rand` version `0.4.3` as indirect dependencies + + 0.1.3 ----- - Show PIN related errors through `pinentry` native reporting mechanism diff --git a/nitrocli/Cargo.lock b/nitrocli/Cargo.lock index 45f9017..a21d95c 100644 --- a/nitrocli/Cargo.lock +++ b/nitrocli/Cargo.lock @@ -1,4 +1,9 @@ [[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "cc" version = "1.0.25" @@ -9,6 +14,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" replace = "cc 1.0.25" [[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "hid" version = "0.4.1" dependencies = [ @@ -49,10 +68,33 @@ dependencies = [ "hid 0.4.1", "hidapi-sys 0.1.4", "libc 0.2.45", + "nitrokey 0.2.1", "pkg-config 0.3.14", ] [[package]] +name = "nitrokey" +version = "0.2.1" +dependencies = [ + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "nitrokey-sys 3.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nitrokey-sys" +version = "3.4.1" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nitrokey-sys" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +replace = "nitrokey-sys 3.4.1" + +[[package]] name = "pkg-config" version = "0.3.14" @@ -62,8 +104,50 @@ version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" replace = "pkg-config 0.3.14" +[[package]] +name = "rand" +version = "0.4.3" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +replace = "rand 0.4.3" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" +"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 hidapi-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "dd8a9410aec7ca9f4571ff40c7b1813a28503c2a664a028921fc973073dcd4bf" "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" +"checksum nitrokey-sys 3.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34794d630d40a093a3f0e31b821b38ee1c16e6909dc42064feff28f4798484f4" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/nitrocli/Cargo.toml b/nitrocli/Cargo.toml index a3414c0..83e6888 100644 --- a/nitrocli/Cargo.toml +++ b/nitrocli/Cargo.toml @@ -49,6 +49,11 @@ version = "0.4" path = "../hid" package = "hid" +[dependencies.libnitrokey] +version = "0.2.1" +path = "../nitrokey" +package = "nitrokey" + [dependencies.pkg-config] version = "0.3" path = "../pkg-config" @@ -58,4 +63,7 @@ path = "../pkg-config" "hidapi-sys:0.1.4" = { path = "../hidapi-sys" } "libc:0.2.45" = { path = "../libc" } "libhid:0.4.1" = { path = "../hid" } +"libnitrokey:0.2.1" = { path = "../nitrokey" } +"nitrokey-sys:3.4.1" = { path = "../nitrokey-sys" } "pkg-config:0.3.14" = { path = "../pkg-config" } +"rand:0.4.3" = { path = "../rand" } diff --git a/nitrokey-sys/.gitignore b/nitrokey-sys/.gitignore new file mode 100644 index 0000000..a821aa9 --- /dev/null +++ b/nitrokey-sys/.gitignore @@ -0,0 +1,4 @@ + +/target +**/*.rs.bk +Cargo.lock diff --git a/nitrokey-sys/CHANGELOG.md b/nitrokey-sys/CHANGELOG.md new file mode 100644 index 0000000..00f832b --- /dev/null +++ b/nitrokey-sys/CHANGELOG.md @@ -0,0 +1,40 @@ +# v3.4.1 (2018-12-10) + +- Update to libnitrokey 3.4.1. There are no changes affecting this crate. + +# v3.4.0 (2018-12-10) + +- Update to libnitrokey 3.4, causing all following changes. +- New constant `NK_device_model_NK_DISCONNECTED` in the `NK_device_model` + enumeration. +- New structures: + - `NK_storage_ProductionTest` + - `NK_storage_status` +- New functions: + - `NK_get_device_model` + - `NK_get_library_version` + - `NK_get_major_library_version` + - `NK_get_minor_libray_version` + - `NK_get_status_storage` + - `NK_get_storage_production_info` + - `NK_totp_set_time_soft` + - `NK_wink` +- The function `NK_totp_get_time` is now deprecated. If applicable, + `NK_totp_set_time_soft` should be used instead. See the [upstream pull + request #114][] for details. +- Strings are now returned as mutable instead of constant pointers. + +# v3.3.0 (2018-05-21) + +- Change the crate license to LGPL 3.0. +- Adapt the crate version number according to the bundled `libnitrokey` + version. +- Include a copy of `libnitrokey`. +- Compile `libnitrokey` from source. +- Generate the `bindgen` bindings statically and add them to the repository. + +# v0.1.0 (2018-05-19) + +- Initial release. + +[upstream pull request #114]: https://github.com/Nitrokey/libnitrokey/pull/114 diff --git a/nitrokey-sys/Cargo.toml b/nitrokey-sys/Cargo.toml new file mode 100644 index 0000000..2486b77 --- /dev/null +++ b/nitrokey-sys/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nitrokey-sys" +version = "3.4.1" +authors = ["Robin Krahl <robin.krahl@ireas.org>"] +homepage = "https://code.ireas.org/nitrokey-rs/" +repository = "https://git.ireas.org/nitrokey-sys-rs/" +description = "Low-level bindings to libnitrokey for communication with Nitrokey devices" +categories = ["external-ffi-bindings"] +license = "LGPL-3.0" +links = "nitrokey" +build = "build.rs" +readme = "README.md" + +[build-dependencies] +cc = "1.0" diff --git a/nitrokey-sys/LICENSE b/nitrokey-sys/LICENSE new file mode 100644 index 0000000..341c30b --- /dev/null +++ b/nitrokey-sys/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/nitrokey-sys/README.md b/nitrokey-sys/README.md new file mode 100644 index 0000000..9b4f654 --- /dev/null +++ b/nitrokey-sys/README.md @@ -0,0 +1,29 @@ +# nitrokey-sys-rs + +Low-level Rust bindings for `libnitrokey`, providing access to Nitrokey +devices. + +```toml +[dependencies] +nitrokey-sys = "3.4.1" +``` + +The version of this crate corresponds to the wrapped `libnitrokey` version. +This crate contains a copy of the `libnitrokey` library, builds it from source +and links it statically. The host system must provide its dependencies in the +library search path: + +- `libhidapi-libusb0` + +## Contact + +For bug reports, patches, feature requests or other messages, please send a +mail to [nitrokey-rs-dev@ireas.org][]. + +## License + +This project as well as `libnitrokey` are licensed under the [LGPL-3.0][]. + +[`libnitrokey`]: https://github.com/nitrokey/libnitrokey +[nitrokey-rs-dev@ireas.org]: mailto:nitrokey-rs-dev@ireas.org +[LGPL-3.0]: https://opensource.org/licenses/lgpl-3.0.html diff --git a/nitrokey-sys/build.rs b/nitrokey-sys/build.rs new file mode 100644 index 0000000..a22bbd0 --- /dev/null +++ b/nitrokey-sys/build.rs @@ -0,0 +1,104 @@ +extern crate cc; + +use std::env; +use std::io; +use std::io::{Read, Write}; +use std::fs; +use std::path; + +struct Version { + major: String, + minor: String, + git: String, +} + +fn stringify(err: env::VarError) -> String { + format!("{}", err) +} + +fn extract_git_version(pre: &str) -> Result<String, String> { + // If a pre-release version is set, it is expected to have the format + // pre.v<maj>.<min>.<n>.g<hash>, where <maj> and <min> are the last major and minor version, + // <n> is the number of commits since this version and <hash> is the hash of the last commit. + let parts: Vec<&str> = pre.split('.').collect(); + if parts.len() != 5 { + return Err(format!("'{}' is not a valid pre-release version", pre)); + } + Ok(format!("{}.{}-{}-{}", parts[1], parts[2], parts[3], parts[4])) +} + +fn get_version() -> Result<Version, String> { + let major = env::var("CARGO_PKG_VERSION_MAJOR").map_err(stringify)?; + let minor = env::var("CARGO_PKG_VERSION_MINOR").map_err(stringify)?; + let patch = env::var("CARGO_PKG_VERSION_PATCH").map_err(stringify)?; + let pre = env::var("CARGO_PKG_VERSION_PRE").map_err(stringify)?; + + let git = match pre.is_empty() { + true => match patch.is_empty() { + true => format!("v{}.{}", major, minor), + false => format!("v{}.{}.{}", major, minor, patch), + }, + false => extract_git_version(&pre)?, + }; + + Ok(Version { + major, + minor, + git, + }) +} + +fn prepare_version_source( + version: &Version, + out_path: &path::Path, + library_path: &path::Path +) -> io::Result<path::PathBuf> { + let out = out_path.join("version.cc"); + let template = library_path.join("version.cc.in"); + + let mut file = fs::File::open(template)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + drop(file); + + let data = data + .replace("@PROJECT_VERSION_MAJOR@", &version.major) + .replace("@PROJECT_VERSION_MINOR@", &version.minor) + .replace("@PROJECT_VERSION_GIT@", &version.git); + + let mut file = fs::File::create(&out)?; + file.write_all(data.as_bytes())?; + + Ok(out) +} + +fn main() { + let out_dir = env::var("OUT_DIR").expect("Environment variable OUT_DIR is not set"); + let out_path = path::PathBuf::from(out_dir); + + let version = get_version().expect("Could not extract library version"); + + let sources = [ + "DeviceCommunicationExceptions.cpp", + "NK_C_API.cc", + "NitrokeyManager.cc", + "command_id.cc", + "device.cc", + "log.cc", + "misc.cc", + ]; + let library_dir = format!("libnitrokey-{}", version.git); + let library_path = path::Path::new(&library_dir); + + let version_source = prepare_version_source(&version, &out_path, &library_path) + .expect("Could not prepare the version source file"); + + cc::Build::new() + .cpp(true) + .include(library_path.join("libnitrokey")) + .files(sources.iter().map(|s| library_path.join(s))) + .file(version_source) + .compile("libnitrokey.a"); + + println!("cargo:rustc-link-lib=hidapi-libusb"); +} diff --git a/nitrokey-sys/libnitrokey-v3.4.1/DeviceCommunicationExceptions.cpp b/nitrokey-sys/libnitrokey-v3.4.1/DeviceCommunicationExceptions.cpp new file mode 100644 index 0000000..4d62aad --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/DeviceCommunicationExceptions.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include "DeviceCommunicationExceptions.h" + +std::atomic_int DeviceCommunicationException::occurred {0}; diff --git a/nitrokey-sys/libnitrokey-v3.4.1/LICENSE b/nitrokey-sys/libnitrokey-v3.4.1/LICENSE new file mode 100644 index 0000000..341c30b --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.cc b/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.cc new file mode 100644 index 0000000..7d0a10e --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.cc @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include "NK_C_API.h" +#include <iostream> +#include <tuple> +#include "libnitrokey/NitrokeyManager.h" +#include <cstring> +#include "libnitrokey/LibraryException.h" +#include "libnitrokey/cxx_semantics.h" +#include "libnitrokey/stick20_commands.h" +#include "version.h" + +#ifdef _MSC_VER +#ifdef _WIN32 +#pragma message "Using own strndup" +char * strndup(const char* str, size_t maxlen) { + size_t len = strnlen(str, maxlen); + char* dup = (char *)malloc(len + 1); + memcpy(dup, str, len); + dup[len] = 0; + return dup; +} +#endif +#endif + +using namespace nitrokey; + +static uint8_t NK_last_command_status = 0; +static const int max_string_field_length = 100; + +template <typename T> +T* duplicate_vector_and_clear(std::vector<T> &v){ + auto d = new T[v.size()]; + std::copy(v.begin(), v.end(), d); + std::fill(v.begin(), v.end(), 0); + return d; +} + +template <typename R, typename T> +std::tuple<int, R> get_with_status(T func, R fallback) { + NK_last_command_status = 0; + try { + return std::make_tuple(0, func()); + } + catch (CommandFailedException & commandFailedException){ + NK_last_command_status = commandFailedException.last_command_status; + } + catch (LibraryException & libraryException){ + NK_last_command_status = libraryException.exception_id(); + } + catch (const DeviceCommunicationException &deviceException){ + NK_last_command_status = 256-deviceException.getType(); + } + return std::make_tuple(NK_last_command_status, fallback); +} + +template <typename T> +uint8_t * get_with_array_result(T func){ + return std::get<1>(get_with_status<uint8_t*>(func, nullptr)); +} + +template <typename T> +char* get_with_string_result(T func){ + auto result = std::get<1>(get_with_status<char*>(func, nullptr)); + if (result == nullptr) { + return strndup("", MAXIMUM_STR_REPLY_LENGTH); + } + return result; +} + +template <typename T> +auto get_with_result(T func){ + return std::get<1>(get_with_status(func, static_cast<decltype(func())>(0))); +} + +template <typename T> +uint8_t get_without_result(T func){ + NK_last_command_status = 0; + try { + func(); + return 0; + } + catch (CommandFailedException & commandFailedException){ + NK_last_command_status = commandFailedException.last_command_status; + } + catch (LibraryException & libraryException){ + NK_last_command_status = libraryException.exception_id(); + } + catch (const InvalidCRCReceived &invalidCRCException){ + ; + } + catch (const DeviceCommunicationException &deviceException){ + NK_last_command_status = 256-deviceException.getType(); + } + return NK_last_command_status; +} + + +#ifdef __cplusplus +extern "C" { +#endif + + NK_C_API uint8_t NK_get_last_command_status() { + auto _copy = NK_last_command_status; + NK_last_command_status = 0; + return _copy; + } + + NK_C_API int NK_login(const char *device_model) { + auto m = NitrokeyManager::instance(); + try { + NK_last_command_status = 0; + return m->connect(device_model); + } + catch (CommandFailedException & commandFailedException) { + NK_last_command_status = commandFailedException.last_command_status; + return commandFailedException.last_command_status; + } + catch (const DeviceCommunicationException &deviceException){ + NK_last_command_status = 256-deviceException.getType(); + cerr << deviceException.what() << endl; + return 0; + } + catch (std::runtime_error &e) { + cerr << e.what() << endl; + return 0; + } + return 0; + } + + NK_C_API int NK_login_enum(NK_device_model device_model) { + const char *model_string; + switch (device_model) { + case NK_PRO: + model_string = "P"; + break; + case NK_STORAGE: + model_string = "S"; + break; + case NK_DISCONNECTED: + default: + /* no such enum value -- return error code */ + return 0; + } + return NK_login(model_string); + } + + NK_C_API int NK_logout() { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->disconnect(); + }); + } + + NK_C_API int NK_first_authenticate(const char* admin_password, const char* admin_temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + return m->first_authenticate(admin_password, admin_temporary_password); + }); + } + + + NK_C_API int NK_user_authenticate(const char* user_password, const char* user_temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->user_authenticate(user_password, user_temporary_password); + }); + } + + NK_C_API int NK_factory_reset(const char* admin_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->factory_reset(admin_password); + }); + } + NK_C_API int NK_build_aes_key(const char* admin_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->build_aes_key(admin_password); + }); + } + + NK_C_API int NK_unlock_user_password(const char *admin_password, const char *new_user_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->unlock_user_password(admin_password, new_user_password); + }); + } + + NK_C_API int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password, + bool delete_user_password, + const char *admin_temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + return m->write_config(numlock, capslock, scrolllock, enable_user_password, delete_user_password, admin_temporary_password); + }); + } + + + NK_C_API uint8_t* NK_read_config() { + auto m = NitrokeyManager::instance(); + return get_with_array_result([&]() { + auto v = m->read_config(); + return duplicate_vector_and_clear(v); + }); + } + + + NK_C_API enum NK_device_model NK_get_device_model() { + auto m = NitrokeyManager::instance(); + try { + auto model = m->get_connected_device_model(); + switch (model) { + case DeviceModel::PRO: + return NK_PRO; + case DeviceModel::STORAGE: + return NK_STORAGE; + default: + /* unknown or not connected device */ + return NK_device_model::NK_DISCONNECTED; + } + } catch (const DeviceNotConnected& e) { + return NK_device_model::NK_DISCONNECTED; + } +} + + + void clear_string(std::string &s) { + std::fill(s.begin(), s.end(), ' '); + } + + + NK_C_API char * NK_status() { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + string && s = m->get_status_as_string(); + char * rs = strndup(s.c_str(), MAXIMUM_STR_REPLY_LENGTH); + clear_string(s); + return rs; + }); + } + + NK_C_API char * NK_device_serial_number() { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + string && s = m->get_serial_number(); + char * rs = strndup(s.c_str(), max_string_field_length); + clear_string(s); + return rs; + }); + } + + NK_C_API char * NK_get_hotp_code(uint8_t slot_number) { + return NK_get_hotp_code_PIN(slot_number, ""); + } + + NK_C_API char * NK_get_hotp_code_PIN(uint8_t slot_number, const char *user_temporary_password) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + string && s = m->get_HOTP_code(slot_number, user_temporary_password); + char * rs = strndup(s.c_str(), max_string_field_length); + clear_string(s); + return rs; + }); + } + + NK_C_API char * NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, + uint8_t last_interval) { + return NK_get_totp_code_PIN(slot_number, challenge, last_totp_time, last_interval, ""); + } + + NK_C_API char * NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, + uint8_t last_interval, const char *user_temporary_password) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + string && s = m->get_TOTP_code(slot_number, challenge, last_totp_time, last_interval, user_temporary_password); + char * rs = strndup(s.c_str(), max_string_field_length); + clear_string(s); + return rs; + }); + } + + NK_C_API int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&] { + m->erase_hotp_slot(slot_number, temporary_password); + }); + } + + NK_C_API int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&] { + m->erase_totp_slot(slot_number, temporary_password); + }); + } + + NK_C_API int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&] { + m->write_HOTP_slot(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, token_ID, + temporary_password); + }); + } + + NK_C_API int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&] { + m->write_TOTP_slot(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, token_ID, + temporary_password); + }); + } + + NK_C_API char* NK_get_totp_slot_name(uint8_t slot_number) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + const auto slot_name = m->get_totp_slot_name(slot_number); + return slot_name; + }); + } + NK_C_API char* NK_get_hotp_slot_name(uint8_t slot_number) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + const auto slot_name = m->get_hotp_slot_name(slot_number); + return slot_name; + }); + } + + NK_C_API void NK_set_debug(bool state) { + auto m = NitrokeyManager::instance(); + m->set_debug(state); + } + + + NK_C_API void NK_set_debug_level(const int level) { + auto m = NitrokeyManager::instance(); + m->set_loglevel(level); + } + + NK_C_API unsigned int NK_get_major_library_version() { + return get_major_library_version(); + } + + NK_C_API unsigned int NK_get_minor_library_version() { + return get_minor_library_version(); + } + + NK_C_API const char* NK_get_library_version() { + return get_library_version(); + } + + NK_C_API int NK_totp_set_time(uint64_t time) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_time(time); + }); + } + + NK_C_API int NK_totp_set_time_soft(uint64_t time) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_time_soft(time); + }); + } + + NK_C_API int NK_totp_get_time() { + return 0; + } + + NK_C_API int NK_change_admin_PIN(const char *current_PIN, const char *new_PIN) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->change_admin_PIN(current_PIN, new_PIN); + }); + } + + NK_C_API int NK_change_user_PIN(const char *current_PIN, const char *new_PIN) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->change_user_PIN(current_PIN, new_PIN); + }); + } + + NK_C_API int NK_enable_password_safe(const char *user_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->enable_password_safe(user_pin); + }); + } + NK_C_API uint8_t * NK_get_password_safe_slot_status() { + auto m = NitrokeyManager::instance(); + return get_with_array_result([&]() { + auto slot_status = m->get_password_safe_slot_status(); + return duplicate_vector_and_clear(slot_status); + }); + + } + + NK_C_API uint8_t NK_get_user_retry_count() { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->get_user_retry_count(); + }); + } + + NK_C_API uint8_t NK_get_admin_retry_count() { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->get_admin_retry_count(); + }); + } + + NK_C_API int NK_lock_device() { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->lock_device(); + }); + } + + NK_C_API char *NK_get_password_safe_slot_name(uint8_t slot_number) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + return m->get_password_safe_slot_name(slot_number); + }); + } + + NK_C_API char *NK_get_password_safe_slot_login(uint8_t slot_number) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + return m->get_password_safe_slot_login(slot_number); + }); + } + NK_C_API char *NK_get_password_safe_slot_password(uint8_t slot_number) { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + return m->get_password_safe_slot_password(slot_number); + }); + } + NK_C_API int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login, + const char *slot_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->write_password_safe_slot(slot_number, slot_name, slot_login, slot_password); + }); + } + + NK_C_API int NK_erase_password_safe_slot(uint8_t slot_number) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->erase_password_safe_slot(slot_number); + }); + } + + NK_C_API int NK_is_AES_supported(const char *user_password) { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return (uint8_t)m->is_AES_supported(user_password); + }); + } + + NK_C_API int NK_login_auto() { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return (uint8_t)m->connect(); + }); + } + + // storage commands + + NK_C_API int NK_send_startup(uint64_t seconds_from_epoch) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->send_startup(seconds_from_epoch); + }); + } + + NK_C_API int NK_unlock_encrypted_volume(const char* user_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->unlock_encrypted_volume(user_pin); + }); + } + + NK_C_API int NK_lock_encrypted_volume() { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->lock_encrypted_volume(); + }); + } + + NK_C_API int NK_unlock_hidden_volume(const char* hidden_volume_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->unlock_hidden_volume(hidden_volume_password); + }); + } + + NK_C_API int NK_lock_hidden_volume() { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->lock_hidden_volume(); + }); + } + + NK_C_API int NK_create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, + const char *hidden_volume_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->create_hidden_volume(slot_nr, start_percent, end_percent, + hidden_volume_password); + }); + } + + NK_C_API int NK_set_unencrypted_read_only(const char *user_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_unencrypted_read_only(user_pin); + }); + } + + NK_C_API int NK_set_unencrypted_read_write(const char *user_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_unencrypted_read_write(user_pin); + }); + } + + NK_C_API int NK_set_unencrypted_read_only_admin(const char *admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_unencrypted_read_only_admin(admin_pin); + }); + } + + NK_C_API int NK_set_unencrypted_read_write_admin(const char *admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_unencrypted_read_write_admin(admin_pin); + }); + } + + NK_C_API int NK_set_encrypted_read_only(const char* admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_encrypted_volume_read_only(admin_pin); + }); + } + + NK_C_API int NK_set_encrypted_read_write(const char* admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->set_encrypted_volume_read_write(admin_pin); + }); + } + + NK_C_API int NK_export_firmware(const char* admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->export_firmware(admin_pin); + }); + } + + NK_C_API int NK_clear_new_sd_card_warning(const char* admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->clear_new_sd_card_warning(admin_pin); + }); + } + + NK_C_API int NK_fill_SD_card_with_random_data(const char* admin_pin) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->fill_SD_card_with_random_data(admin_pin); + }); + } + + NK_C_API int NK_change_update_password(const char* current_update_password, + const char* new_update_password) { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->change_update_password(current_update_password, new_update_password); + }); + } + + NK_C_API int NK_enable_firmware_update(const char* update_password){ + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + m->enable_firmware_update(update_password); + }); + } + + NK_C_API char* NK_get_status_storage_as_string() { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + return m->get_status_storage_as_string(); + }); + } + + NK_C_API int NK_get_status_storage(NK_storage_status* out) { + if (out == nullptr) { + return -1; + } + auto m = NitrokeyManager::instance(); + auto result = get_with_status([&]() { + return m->get_status_storage(); + }, proto::stick20::DeviceConfigurationResponsePacket::ResponsePayload()); + auto error_code = std::get<0>(result); + if (error_code != 0) { + return error_code; + } + + auto status = std::get<1>(result); + out->unencrypted_volume_read_only = status.ReadWriteFlagUncryptedVolume_u8 != 0; + out->unencrypted_volume_active = status.VolumeActiceFlag_st.unencrypted; + out->encrypted_volume_read_only = status.ReadWriteFlagCryptedVolume_u8 != 0; + out->encrypted_volume_active = status.VolumeActiceFlag_st.encrypted; + out->hidden_volume_read_only = status.ReadWriteFlagHiddenVolume_u8 != 0; + out->hidden_volume_active = status.VolumeActiceFlag_st.hidden; + out->firmware_version_major = status.versionInfo.major; + out->firmware_version_minor = status.versionInfo.minor; + out->firmware_locked = status.FirmwareLocked_u8 != 0; + out->serial_number_sd_card = status.ActiveSD_CardID_u32; + out->serial_number_smart_card = status.ActiveSmartCardID_u32; + out->user_retry_count = status.UserPwRetryCount; + out->admin_retry_count = status.AdminPwRetryCount; + out->new_sd_card_found = status.NewSDCardFound_st.NewCard; + out->filled_with_random = (status.SDFillWithRandomChars_u8 & 0x01) != 0; + out->stick_initialized = status.StickKeysNotInitiated == 0; + return 0; + } + + NK_C_API int NK_get_storage_production_info(NK_storage_ProductionTest * out){ + if (out == nullptr) { + return -1; + } + auto m = NitrokeyManager::instance(); + auto result = get_with_status([&]() { + return m->production_info(); + }, proto::stick20::ProductionTest::ResponsePayload()); + + auto error_code = std::get<0>(result); + if (error_code != 0) { + return error_code; + } + + stick20::ProductionTest::ResponsePayload status = std::get<1>(result); + // Cannot use memcpy without declaring C API struct packed + // (which is not parsed by Python's CFFI apparently), hence the manual way. +#define a(x) out->x = status.x; + a(FirmwareVersion_au8[0]); + a(FirmwareVersion_au8[1]); + a(FirmwareVersionInternal_u8); + a(SD_Card_Size_u8); + a(CPU_CardID_u32); + a(SmartCardID_u32); + a(SD_CardID_u32); + a(SC_UserPwRetryCount); + a(SC_AdminPwRetryCount); + a(SD_Card_ManufacturingYear_u8); + a(SD_Card_ManufacturingMonth_u8); + a(SD_Card_OEM_u16); + a(SD_WriteSpeed_u16); + a(SD_Card_Manufacturer_u8); +#undef a + return 0; + } + + +NK_C_API char* NK_get_SD_usage_data_as_string() { + auto m = NitrokeyManager::instance(); + return get_with_string_result([&]() { + return m->get_SD_usage_data_as_string(); + }); + } + + NK_C_API int NK_get_progress_bar_value() { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->get_progress_bar_value(); + }); + } + + NK_C_API int 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() { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->get_minor_firmware_version(); + }); + } + + NK_C_API int NK_set_unencrypted_volume_rorw_pin_type_user() { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->set_unencrypted_volume_rorw_pin_type_user() ? 1 : 0; + }); + } + + NK_C_API char* NK_list_devices_by_cpuID() { + auto nm = NitrokeyManager::instance(); + return get_with_string_result([&]() { + auto v = nm->list_devices_by_cpuID(); + std::string res; + for (const auto a : v){ + res += a+";"; + } + if (res.size()>0) res.pop_back(); // remove last delimiter char + return strndup(res.c_str(), MAXIMUM_STR_REPLY_LENGTH); + }); + } + + NK_C_API int NK_connect_with_ID(const char* id) { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->connect_with_ID(id) ? 1 : 0; + }); + } + + NK_C_API int NK_wink() { + auto m = NitrokeyManager::instance(); + return get_without_result([&]() { + return m->wink(); + }); + } + +#ifdef __cplusplus +} +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.h b/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.h new file mode 100644 index 0000000..b1bdf1e --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/NK_C_API.h @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LIBNITROKEY_NK_C_API_H +#define LIBNITROKEY_NK_C_API_H + +#include <stdbool.h> +#include <stdint.h> + +#include "deprecated.h" + +#ifdef _MSC_VER +#define NK_C_API __declspec(dllexport) +#else +#define NK_C_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + static const int MAXIMUM_STR_REPLY_LENGTH = 8192; + + /** + * The Nitrokey device models supported by the API. + */ + enum NK_device_model { + /** + * Use, if no supported device is connected + */ + NK_DISCONNECTED = 0, + /** + * Nitrokey Pro. + */ + NK_PRO = 1, + /** + * Nitrokey Storage. + */ + NK_STORAGE = 2 + }; + + /** + * Stores the status of a Storage device. + */ + struct NK_storage_status { + /** + * Indicates whether the unencrypted volume is read-only. + */ + bool unencrypted_volume_read_only; + /** + * Indicates whether the unencrypted volume is active. + */ + bool unencrypted_volume_active; + /** + * Indicates whether the encrypted volume is read-only. + */ + bool encrypted_volume_read_only; + /** + * Indicates whether the encrypted volume is active. + */ + bool encrypted_volume_active; + /** + * Indicates whether the hidden volume is read-only. + */ + bool hidden_volume_read_only; + /** + * Indicates whether the hidden volume is active. + */ + bool hidden_volume_active; + /** + * 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; + /** + * Indicates whether the firmware is locked. + */ + bool firmware_locked; + /** + * The serial number of the SD card in the Storage stick. + */ + uint32_t serial_number_sd_card; + /** + * The serial number of the smart card in the Storage stick. + */ + uint32_t serial_number_smart_card; + /** + * The number of remaining login attempts for the user PIN. + */ + uint8_t user_retry_count; + /** + * The number of remaining login attempts for the admin PIN. + */ + uint8_t admin_retry_count; + /** + * Indicates whether a new SD card was found. + */ + bool new_sd_card_found; + /** + * Indicates whether the SD card is filled with random characters. + */ + bool filled_with_random; + /** + * Indicates whether the stick has been initialized by generating + * the AES keys. + */ + bool stick_initialized; + }; + + struct NK_storage_ProductionTest{ + uint8_t FirmwareVersion_au8[2]; + uint8_t FirmwareVersionInternal_u8; + uint8_t SD_Card_Size_u8; + uint32_t CPU_CardID_u32; + uint32_t SmartCardID_u32; + uint32_t SD_CardID_u32; + uint8_t SC_UserPwRetryCount; + uint8_t SC_AdminPwRetryCount; + uint8_t SD_Card_ManufacturingYear_u8; + uint8_t SD_Card_ManufacturingMonth_u8; + uint16_t SD_Card_OEM_u16; + uint16_t SD_WriteSpeed_u16; + uint8_t SD_Card_Manufacturer_u8; + }; + + NK_C_API int NK_get_storage_production_info(struct NK_storage_ProductionTest * out); + + +/** + * Set debug level of messages written on stderr + * @param state state=True - most messages, state=False - only errors level + */ + NK_C_API void NK_set_debug(bool state); + + /** + * Set debug level of messages written on stderr + * @param level (int) 0-lowest verbosity, 5-highest verbosity + */ + NK_C_API void NK_set_debug_level(const int level); + + /** + * Get the major library version, e. g. the 3 in v3.2. + * @return the major library version + */ + NK_C_API unsigned int NK_get_major_library_version(); + + /** + * Get the minor library version, e. g. the 2 in v3.2. + * @return the minor library version + */ + NK_C_API unsigned int NK_get_minor_library_version(); + + /** + * Get the library version as a string. This is the output of + * `git describe --always` at compile time, for example "v3.3" or + * "v3.3-19-gaee920b". + * The return value is a string literal and must not be freed. + * @return the library version as a string + */ + NK_C_API const char* NK_get_library_version(); + + /** + * Connect to device of given model. Currently library can be connected only to one device at once. + * @param device_model char 'S': Nitrokey Storage, 'P': Nitrokey Pro + * @return 1 if connected, 0 if wrong model or cannot connect + */ + NK_C_API int NK_login(const char *device_model); + + /** + * Connect to device of given model. Currently library can be connected only to one device at once. + * @param device_model NK_device_model: NK_PRO: Nitrokey Pro, NK_STORAGE: Nitrokey Storage + * @return 1 if connected, 0 if wrong model or cannot connect + */ + NK_C_API int NK_login_enum(enum NK_device_model device_model); + + /** + * Connect to first available device, starting checking from Pro 1st to Storage 2nd. + * @return 1 if connected, 0 if wrong model or cannot connect + */ + NK_C_API int NK_login_auto(); + + /** + * Disconnect from the device. + * @return command processing error code + */ + NK_C_API int NK_logout(); + + /** + * Query the model of the connected device. + * Returns the model of the connected device or NK_DISCONNECTED. + * + * @return true if a device is connected and the out argument has been set + */ + NK_C_API enum NK_device_model NK_get_device_model(); + + /** + * Return the debug status string. Debug purposes. + * @return command processing error code + */ + NK_C_API char * NK_status(); + + /** + * Return the device's serial number string in hex. + * @return string device's serial number in hex + */ + NK_C_API char * NK_device_serial_number(); + + /** + * Get last command processing status. Useful for commands which returns the results of their own and could not return + * an error code. + * @return previous command processing error code + */ + NK_C_API uint8_t NK_get_last_command_status(); + + /** + * Lock device - cancel any user device unlocking. + * @return command processing error code + */ + NK_C_API int NK_lock_device(); + + /** + * Authenticates the user on USER privilages with user_password and sets user's temporary password on device to user_temporary_password. + * @param user_password char[25] current user password + * @param user_temporary_password char[25] user temporary password to be set on device for further communication (authentication command) + * @return command processing error code + */ + NK_C_API int NK_user_authenticate(const char* user_password, const char* user_temporary_password); + + /** + * Authenticates the user on ADMIN privilages with admin_password and sets user's temporary password on device to admin_temporary_password. + * @param admin_password char[25] current administrator PIN + * @param admin_temporary_password char[25] admin temporary password to be set on device for further communication (authentication command) + * @return command processing error code + */ + NK_C_API int NK_first_authenticate(const char* admin_password, const char* admin_temporary_password); + + /** + * Execute a factory reset. + * @param admin_password char[20] current administrator PIN + * @return command processing error code + */ + NK_C_API int NK_factory_reset(const char* admin_password); + + /** + * Generates AES key on the device + * @param admin_password char[20] current administrator PIN + * @return command processing error code + */ + NK_C_API int NK_build_aes_key(const char* admin_password); + + /** + * Unlock user PIN locked after 3 incorrect codes tries. + * @param admin_password char[20] current administrator PIN + * @return command processing error code + */ + NK_C_API int NK_unlock_user_password(const char *admin_password, const char *new_user_password); + + /** + * Write general config to the device + * @param numlock set value in range [0-1] to send HOTP code from slot 'numlock' after double pressing numlock + * or outside the range to disable this function + * @param capslock similar to numlock but with capslock + * @param scrolllock similar to numlock but with scrolllock + * @param enable_user_password set True to enable OTP PIN protection (require PIN each OTP code request) + * @param delete_user_password (unused) + * @param admin_temporary_password current admin temporary password + * @return command processing error code + */ + NK_C_API int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, + bool enable_user_password, bool delete_user_password, const char *admin_temporary_password); + + /** + * Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and is enabled PIN protected OTP + * @see NK_write_config + * @return uint8_t general_config[5]: + * uint8_t numlock; + uint8_t capslock; + uint8_t scrolllock; + uint8_t enable_user_password; + uint8_t delete_user_password; + + */ + NK_C_API uint8_t* NK_read_config(); + + //OTP + + /** + * Get name of given TOTP slot + * @param slot_number TOTP slot number, slot_number<15 + * @return char[20] the name of the slot + */ + NK_C_API char * NK_get_totp_slot_name(uint8_t slot_number); + + /** + * + * @param slot_number HOTP slot number, slot_number<3 + * @return char[20] the name of the slot + */ + NK_C_API char * NK_get_hotp_slot_name(uint8_t slot_number); + + /** + * Erase HOTP slot data from the device + * @param slot_number HOTP slot number, slot_number<3 + * @param temporary_password admin temporary password + * @return command processing error code + */ + NK_C_API int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_password); + + /** + * Erase TOTP slot data from the device + * @param slot_number TOTP slot number, slot_number<15 + * @param temporary_password admin temporary password + * @return command processing error code + */ + NK_C_API int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_password); + + /** + * Write HOTP slot data to the device + * @param slot_number HOTP slot number, slot_number<3, 0-numbered + * @param slot_name char[15] desired slot name. C string (requires ending '\0'; 16 bytes). + * @param secret char[40] 160-bit or 320-bit (currently Pro v0.8 only) secret as a hex string. C string (requires ending '\0'; 41 bytes). + * See NitrokeyManager::is_320_OTP_secret_supported. + * @param hotp_counter uint32_t starting value of HOTP counter + * @param use_8_digits should returned codes be 6 (false) or 8 digits (true) + * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock + * @param use_tokenID @see token_ID + * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section + * @param temporary_password char[25] admin temporary password + * @return command processing error code + */ + NK_C_API int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + + /** + * Write TOTP slot data to the device + * @param slot_number TOTP slot number, slot_number<15, 0-numbered + * @param slot_name char[15] desired slot name. C string (requires ending '\0'; 16 bytes). + * @param secret char[40] 160-bit or 320-bit (currently Pro v0.8 only) secret as a hex string. C string (requires ending '\0'; 41 bytes). + * See NitrokeyManager::is_320_OTP_secret_supported. + * @param time_window uint16_t time window for this TOTP + * @param use_8_digits should returned codes be 6 (false) or 8 digits (true) + * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock + * @param use_tokenID @see token_ID + * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section + * @param temporary_password char[20] admin temporary password + * @return command processing error code + */ + NK_C_API int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + + /** + * Get HOTP code from the device + * @param slot_number HOTP slot number, slot_number<3 + * @return HOTP code + */ + NK_C_API char * NK_get_hotp_code(uint8_t slot_number); + + /** + * Get HOTP code from the device (PIN protected) + * @param slot_number HOTP slot number, slot_number<3 + * @param user_temporary_password char[25] user temporary password if PIN protected OTP codes are enabled, + * otherwise should be set to empty string - '' + * @return HOTP code + */ + NK_C_API char * NK_get_hotp_code_PIN(uint8_t slot_number, const char *user_temporary_password); + + /** + * Get TOTP code from the device + * @param slot_number TOTP slot number, slot_number<15 + * @param challenge TOTP challenge -- unused + * @param last_totp_time last time -- unused + * @param last_interval last interval --unused + * @return TOTP code + */ + NK_C_API char * NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, + uint8_t last_interval); + + /** + * Get TOTP code from the device (PIN protected) + * @param slot_number TOTP slot number, slot_number<15 + * @param challenge TOTP challenge -- unused + * @param last_totp_time last time -- unused + * @param last_interval last interval -- unused + * @param user_temporary_password char[25] user temporary password if PIN protected OTP codes are enabled, + * otherwise should be set to empty string - '' + * @return TOTP code + */ + NK_C_API char * NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, + uint64_t last_totp_time, uint8_t last_interval, + const char *user_temporary_password); + + /** + * Set time on the device (for TOTP requests) + * @param time seconds in unix epoch (from 01.01.1970) + * @return command processing error code + */ + NK_C_API int NK_totp_set_time(uint64_t time); + + /** + * Set the device time used for TOTP to the given time. Contrary to + * {@code set_time(uint64_t)}, this command fails if {@code old_time} + * > {@code time} or if {@code old_time} is zero (where {@code + * old_time} is the current time on the device). + * + * @param time new device time as Unix timestamp (seconds since + * 1970-01-01) + * @return command processing error code + */ + NK_C_API int NK_totp_set_time_soft(uint64_t time); + + // NK_totp_get_time is deprecated -- use NK_totp_set_time_soft instead + DEPRECATED + NK_C_API int NK_totp_get_time(); + + //passwords + /** + * Change administrator PIN + * @param current_PIN char[25] current PIN + * @param new_PIN char[25] new PIN + * @return command processing error code + */ + NK_C_API int NK_change_admin_PIN(const char *current_PIN, const char *new_PIN); + + /** + * Change user PIN + * @param current_PIN char[25] current PIN + * @param new_PIN char[25] new PIN + * @return command processing error code + */ + NK_C_API int NK_change_user_PIN(const char *current_PIN, const char *new_PIN); + + + /** + * Get retry count of user PIN + * @return user PIN retry count + */ + NK_C_API uint8_t NK_get_user_retry_count(); + + /** + * Get retry count of admin PIN + * @return admin PIN retry count + */ + NK_C_API uint8_t NK_get_admin_retry_count(); + //password safe + + /** + * Enable password safe access + * @param user_pin char[30] current user PIN + * @return command processing error code + */ + NK_C_API int NK_enable_password_safe(const char *user_pin); + + /** + * Get password safe slots' status + * @return uint8_t[16] slot statuses - each byte represents one slot with 0 (not programmed) and 1 (programmed) + */ + NK_C_API uint8_t * NK_get_password_safe_slot_status(); + + /** + * Get password safe slot name + * @param slot_number password safe slot number, slot_number<16 + * @return slot name + */ + NK_C_API char *NK_get_password_safe_slot_name(uint8_t slot_number); + + /** + * Get password safe slot login + * @param slot_number password safe slot number, slot_number<16 + * @return login from the PWS slot + */ + NK_C_API char *NK_get_password_safe_slot_login(uint8_t slot_number); + + /** + * Get the password safe slot password + * @param slot_number password safe slot number, slot_number<16 + * @return password from the PWS slot + */ + NK_C_API char *NK_get_password_safe_slot_password(uint8_t slot_number); + + /** + * Write password safe data to the slot + * @param slot_number password safe slot number, slot_number<16 + * @param slot_name char[11] name of the slot + * @param slot_login char[32] login string + * @param slot_password char[20] password string + * @return command processing error code + */ + NK_C_API int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name, + const char *slot_login, const char *slot_password); + + /** + * Erase the password safe slot from the device + * @param slot_number password safe slot number, slot_number<16 + * @return command processing error code + */ + NK_C_API int NK_erase_password_safe_slot(uint8_t slot_number); + + /** + * Check whether AES is supported by the device + * @return 0 for no and 1 for yes + */ + NK_C_API int NK_is_AES_supported(const char *user_password); + + /** + * 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(); + + /** + * 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(); + + /** + * Function to determine unencrypted volume PIN type + * @param minor_firmware_version + * @return Returns 1, if set unencrypted volume ro/rw pin type is User, 0 otherwise. + */ + NK_C_API int NK_set_unencrypted_volume_rorw_pin_type_user(); + + + /** + * This command is typically run to initiate + * communication with the device (altough not required). + * It sets time on device and returns its current status + * - a combination of set_time and get_status_storage commands + * Storage only + * @param seconds_from_epoch date and time expressed in seconds + */ + NK_C_API int NK_send_startup(uint64_t seconds_from_epoch); + + /** + * Unlock encrypted volume. + * Storage only + * @param user_pin user pin 20 characters + * @return command processing error code + */ + NK_C_API int NK_unlock_encrypted_volume(const char* user_pin); + + /** + * Locks encrypted volume + * @return command processing error code + */ + NK_C_API int NK_lock_encrypted_volume(); + + /** + * Unlock hidden volume and lock encrypted volume. + * Requires encrypted volume to be unlocked. + * Storage only + * @param hidden_volume_password 20 characters + * @return command processing error code + */ + NK_C_API int NK_unlock_hidden_volume(const char* hidden_volume_password); + + /** + * Locks hidden volume + * @return command processing error code + */ + NK_C_API int NK_lock_hidden_volume(); + + /** + * Create hidden volume. + * Requires encrypted volume to be unlocked. + * Storage only + * @param slot_nr slot number in range 0-3 + * @param start_percent volume begin expressed in percent of total available storage, int in range 0-99 + * @param end_percent volume end expressed in percent of total available storage, int in range 1-100 + * @param hidden_volume_password 20 characters + * @return command processing error code + */ + NK_C_API int NK_create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, + const char *hidden_volume_password); + + /** + * Make unencrypted volume read-only. + * Device hides unencrypted volume for a second therefore make sure + * buffers are flushed before running. + * Does nothing if firmware version is not matched + * Firmware range: Storage v0.50, v0.48 and below + * Storage only + * @param user_pin 20 characters User PIN + * @return command processing error code + */ + NK_C_API int NK_set_unencrypted_read_only(const char *user_pin); + + /** + * Make unencrypted volume read-write. + * Device hides unencrypted volume for a second therefore make sure + * buffers are flushed before running. + * Does nothing if firmware version is not matched + * Firmware range: Storage v0.50, v0.48 and below + * Storage only + * @param user_pin 20 characters User PIN + * @return command processing error code + */ + NK_C_API int NK_set_unencrypted_read_write(const char *user_pin); + + /** + * Make unencrypted volume read-only. + * Device hides unencrypted volume for a second therefore make sure + * buffers are flushed before running. + * Does nothing if firmware version is not matched + * Firmware range: Storage v0.49, v0.51+ + * Storage only + * @param admin_pin 20 characters Admin PIN + * @return command processing error code + */ + NK_C_API int NK_set_unencrypted_read_only_admin(const char* admin_pin); + + /** + * Make unencrypted volume read-write. + * Device hides unencrypted volume for a second therefore make sure + * buffers are flushed before running. + * Does nothing if firmware version is not matched + * Firmware range: Storage v0.49, v0.51+ + * Storage only + * @param admin_pin 20 characters Admin PIN + * @return command processing error code + */ + NK_C_API int NK_set_unencrypted_read_write_admin(const char* admin_pin); + + /** + * Make encrypted volume read-only. + * Device hides encrypted volume for a second therefore make sure + * buffers are flushed before running. + * Firmware range: v0.49 only, future (see firmware release notes) + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ + NK_C_API int NK_set_encrypted_read_only(const char* admin_pin); + + /** + * Make encrypted volume read-write. + * Device hides encrypted volume for a second therefore make sure + * buffers are flushed before running. + * Firmware range: v0.49 only, future (see firmware release notes) + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ + NK_C_API int NK_set_encrypted_read_write(const char* admin_pin); + + /** + * Exports device's firmware to unencrypted volume. + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ + NK_C_API int NK_export_firmware(const char* admin_pin); + + /** + * Clear new SD card notification. It is set after factory reset. + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ + NK_C_API int NK_clear_new_sd_card_warning(const char* admin_pin); + + /** + * Fill SD card with random data. + * Should be done on first stick initialization after creating keys. + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ + NK_C_API int NK_fill_SD_card_with_random_data(const char* admin_pin); + + /** + * Change update password. + * Update password is used for entering update mode, where firmware + * could be uploaded using dfu-programmer or other means. + * Storage only + * @param current_update_password 20 characters + * @param new_update_password 20 characters + * @return command processing error code + */ + NK_C_API int NK_change_update_password(const char* current_update_password, + const char* new_update_password); + + /** + * Enter update mode. Needs update password. + * When device is in update mode it no longer accepts any HID commands until + * firmware is launched (regardless of being updated or not). + * Smartcard (through CCID interface) and its all volumes are not visible as well. + * Its VID and PID are changed to factory-default (03eb:2ff1 Atmel Corp.) + * to be detected by flashing software. Result of this command can be reversed + * by using 'launch' command. + * For dfu-programmer it would be: 'dfu-programmer at32uc3a3256s launch'. + * Storage only + * @param update_password 20 characters + * @return command processing error code + */ + NK_C_API int NK_enable_firmware_update(const char* update_password); + + /** + * Get Storage stick status as string. + * Storage only + * @return string with devices attributes + */ + NK_C_API char* NK_get_status_storage_as_string(); + + /** + * Get the Storage stick status 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 storage status + * @return command processing error code + */ + NK_C_API int NK_get_status_storage(struct NK_storage_status* out); + + /** + * Get SD card usage attributes as string. + * Usable during hidden volumes creation. + * Storage only + * @return string with SD card usage attributes + */ + NK_C_API char* NK_get_SD_usage_data_as_string(); + + /** + * Get progress value of current long operation. + * Storage only + * @return int in range 0-100 or -1 if device is not busy + */ + NK_C_API int NK_get_progress_bar_value(); + +/** + * Returns a list of connected devices' id's, delimited by ';' character. Empty string is returned on no device found. + * Each ID could consist of: + * 1. SC_id:SD_id_p_path (about 40 bytes) + * 2. path (about 10 bytes) + * where 'path' is USB path (bus:num), 'SC_id' is smartcard ID, 'SD_id' is storage card ID and + * '_p_' and ':' are field delimiters. + * Case 2 (USB path only) is used, when the device cannot be asked about its status data (e.g. during a long operation, + * like clearing SD card. + * Internally connects to all available devices and creates a map between ids and connection objects. + * Side effects: changes active device to last detected Storage device. + * Storage only + * @example Example of returned data: '00005d19:dacc2cb4_p_0001:0010:02;000037c7:4cf12445_p_0001:000f:02;0001:000c:02' + * @return string delimited id's of connected devices + */ + NK_C_API char* NK_list_devices_by_cpuID(); + + +/** + * 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. + * Storage only + * @param id Target device ID (example: '00005d19:dacc2cb4_p_0001:0010:02') + * @return 1 on successful connection, 0 otherwise + */ + NK_C_API int NK_connect_with_ID(const char* id); + + /** + * Blink red and green LED alternatively and infinitely (until device is reconnected). + * @return command processing error code + */ + NK_C_API int NK_wink(); + +#ifdef __cplusplus +} +#endif + +#endif //LIBNITROKEY_NK_C_API_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/NitrokeyManager.cc b/nitrokey-sys/libnitrokey-v3.4.1/NitrokeyManager.cc new file mode 100644 index 0000000..a950e4b --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/NitrokeyManager.cc @@ -0,0 +1,1149 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include <cstring> +#include <iostream> +#include "libnitrokey/NitrokeyManager.h" +#include "libnitrokey/LibraryException.h" +#include <algorithm> +#include <unordered_map> +#include <stick20_commands.h> +#include "libnitrokey/misc.h" +#include <mutex> +#include "libnitrokey/cxx_semantics.h" +#include <functional> +#include <stick10_commands.h> + +std::mutex nitrokey::proto::send_receive_mtx; + +namespace nitrokey{ + + std::mutex mex_dev_com_manager; + +#ifndef strndup +#ifdef _WIN32 +#pragma message "Using own strndup" +char * strndup(const char* str, size_t maxlen){ + size_t len = strnlen(str, maxlen); + char* dup = (char *) malloc(len + 1); + memcpy(dup, str, len); + dup[len] = 0; + return dup; +} +#endif +#endif + +using nitrokey::misc::strcpyT; + + template <typename T> + typename T::CommandPayload get_payload(){ + //Create, initialize and return by value command payload + typename T::CommandPayload st; + bzero(&st, sizeof(st)); + return st; + } + + + // package type to auth, auth type [Authorize,UserAuthorize] + template <typename S, typename A, typename T> + void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device){ + if (!is_authorization_command_supported()){ + LOG("Authorization command not supported, skipping", Loglevel::WARNING); + } + auto auth = get_payload<A>(); + strcpyT(auth.temporary_password, admin_temporary_password); + auth.crc_to_authorize = S::CommandTransaction::getCRC(package); + A::CommandTransaction::run(device, auth); + } + + shared_ptr <NitrokeyManager> NitrokeyManager::_instance = nullptr; + + NitrokeyManager::NitrokeyManager() : device(nullptr) + { + set_debug(false); + } + NitrokeyManager::~NitrokeyManager() { + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + + for (auto d : connected_devices){ + if (d.second == nullptr) continue; + d.second->disconnect(); + connected_devices[d.first] = nullptr; + } + } + + bool NitrokeyManager::set_current_device_speed(int retry_delay, int send_receive_delay){ + if (retry_delay < 20 || send_receive_delay < 20){ + LOG("Delay set too low: " + to_string(retry_delay) +" "+ to_string(send_receive_delay), Loglevel::WARNING); + return false; + } + + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + if(device == nullptr) { + return false; + } + device->set_receiving_delay(std::chrono::duration<int, std::milli>(send_receive_delay)); + device->set_retry_delay(std::chrono::duration<int, std::milli>(retry_delay)); + return true; + } + + std::vector<std::string> NitrokeyManager::list_devices(){ + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + + auto p = make_shared<Stick20>(); + return p->enumerate(); // make static + } + + std::vector<std::string> NitrokeyManager::list_devices_by_cpuID(){ + using misc::toHex; + //disconnect default device + disconnect(); + + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + LOGD1("Disconnecting registered devices"); + for (auto & kv : connected_devices_byID){ + if (kv.second != nullptr) + kv.second->disconnect(); + } + connected_devices_byID.clear(); + + LOGD1("Enumerating devices"); + std::vector<std::string> res; + auto d = make_shared<Stick20>(); + const auto v = d->enumerate(); + LOGD1("Discovering IDs"); + for (auto & p: v){ + d = make_shared<Stick20>(); + LOGD1( std::string("Found: ") + p ); + d->set_path(p); + try{ + if (d->connect()){ + device = d; + std::string id; + try { + const auto status = get_status_storage(); + const auto sc_id = toHex(status.ActiveSmartCardID_u32); + const auto sd_id = toHex(status.ActiveSD_CardID_u32); + id += sc_id + ":" + sd_id; + id += "_p_" + p; + } + catch (const LongOperationInProgressException &e) { + LOGD1(std::string("Long operation in progress, setting ID to: ") + p); + id = p; + } + + connected_devices_byID[id] = d; + res.push_back(id); + LOGD1( std::string("Found: ") + p + " => " + id); + } else{ + LOGD1( std::string("Could not connect to: ") + p); + } + } + catch (const DeviceCommunicationException &e){ + LOGD1( std::string("Exception encountered: ") + p); + } + } + return res; + } + + bool NitrokeyManager::connect_with_ID(const std::string id) { + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + + auto position = connected_devices_byID.find(id); + if (position == connected_devices_byID.end()) { + LOGD1(std::string("Could not find device ")+id + ". Refresh devices list with list_devices_by_cpuID()."); + return false; + } + + auto d = connected_devices_byID[id]; + device = d; + current_device_id = id; + + //validate connection + try{ + get_status(); + } + catch (const LongOperationInProgressException &){ + //ignore + } + catch (const DeviceCommunicationException &){ + d->disconnect(); + current_device_id = ""; + connected_devices_byID[id] = nullptr; + connected_devices_byID.erase(position); + return false; + } + nitrokey::log::Log::setPrefix(id); + LOGD1("Device successfully changed"); + return true; + } + + /** + * Connects device to path. + * Assumes devices are not being disconnected and caches connections (param cache_connections). + * @param path os-dependent device path + * @return false, when could not connect, true otherwise + */ + bool NitrokeyManager::connect_with_path(std::string path) { + const bool cache_connections = false; + + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + + if (cache_connections){ + if(connected_devices.find(path) != connected_devices.end() + && connected_devices[path] != nullptr) { + device = connected_devices[path]; + return true; + } + } + + auto p = make_shared<Stick20>(); + p->set_path(path); + + if(!p->connect()) return false; + + if(cache_connections){ + connected_devices [path] = p; + } + + device = p; //previous device will be disconnected automatically + current_device_id = path; + nitrokey::log::Log::setPrefix(path); + LOGD1("Device successfully changed"); + return true; + } + + bool NitrokeyManager::connect() { + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + vector< shared_ptr<Device> > devices = { make_shared<Stick10>(), make_shared<Stick20>() }; + bool connected = false; + for( auto & d : devices ){ + if (d->connect()){ + device = std::shared_ptr<Device>(d); + connected = true; + } + } + return connected; + } + + + void NitrokeyManager::set_log_function(std::function<void(std::string)> log_function){ + static nitrokey::log::FunctionalLogHandler handler(log_function); + nitrokey::log::Log::instance().set_handler(&handler); + } + + bool NitrokeyManager::set_default_commands_delay(int delay){ + if (delay < 20){ + LOG("Delay set too low: " + to_string(delay), Loglevel::WARNING); + return false; + } + Device::set_default_device_speed(delay); + return true; + } + + bool NitrokeyManager::connect(const char *device_model) { + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + LOG(__FUNCTION__, nitrokey::log::Loglevel::DEBUG_L2); + switch (device_model[0]){ + case 'P': + device = make_shared<Stick10>(); + break; + case 'S': + device = make_shared<Stick20>(); + break; + default: + throw std::runtime_error("Unknown model"); + } + return device->connect(); + } + + bool NitrokeyManager::connect(device::DeviceModel device_model) { + const char *model_string; + switch (device_model) { + case device::DeviceModel::PRO: + model_string = "P"; + break; + case device::DeviceModel::STORAGE: + model_string = "S"; + break; + default: + throw std::runtime_error("Unknown model"); + } + return connect(model_string); + } + + shared_ptr<NitrokeyManager> NitrokeyManager::instance() { + static std::mutex mutex; + std::lock_guard<std::mutex> lock(mutex); + if (_instance == nullptr){ + _instance = make_shared<NitrokeyManager>(); + } + return _instance; + } + + + + bool NitrokeyManager::disconnect() { + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + return _disconnect_no_lock(); + } + + bool NitrokeyManager::_disconnect_no_lock() { + //do not use directly without locked mutex, + //used by could_be_enumerated, disconnect + if (device == nullptr){ + return false; + } + const auto res = device->disconnect(); + device = nullptr; + return res; + } + + bool NitrokeyManager::is_connected() throw(){ + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + if(device != nullptr){ + auto connected = device->could_be_enumerated(); + if(connected){ + return true; + } else { + _disconnect_no_lock(); + return false; + } + } + return false; + } + + bool NitrokeyManager::could_current_device_be_enumerated() { + std::lock_guard<std::mutex> lock(mex_dev_com_manager); + if (device != nullptr) { + return device->could_be_enumerated(); + } + return false; + } + + void NitrokeyManager::set_loglevel(int loglevel) { + loglevel = max(loglevel, static_cast<int>(Loglevel::ERROR)); + loglevel = min(loglevel, static_cast<int>(Loglevel::DEBUG_L2)); + Log::instance().set_loglevel(static_cast<Loglevel>(loglevel)); + } + + void NitrokeyManager::set_loglevel(Loglevel loglevel) { + Log::instance().set_loglevel(loglevel); + } + + void NitrokeyManager::set_debug(bool state) { + if (state){ + Log::instance().set_loglevel(Loglevel::DEBUG); + } else { + Log::instance().set_loglevel(Loglevel::ERROR); + } + } + + + string NitrokeyManager::get_serial_number() { + if (device == nullptr) { return ""; }; + switch (device->get_device_model()) { + case DeviceModel::PRO: { + auto response = GetStatus::CommandTransaction::run(device); + return nitrokey::misc::toHex(response.data().card_serial_u32); + } + break; + + case DeviceModel::STORAGE: + { + auto response = stick20::GetDeviceStatus::CommandTransaction::run(device); + return nitrokey::misc::toHex(response.data().ActiveSmartCardID_u32); + } + break; + } + return "NA"; + } + + stick10::GetStatus::ResponsePayload NitrokeyManager::get_status(){ + try{ + auto response = GetStatus::CommandTransaction::run(device); + return response.data(); + } + catch (DeviceSendingFailure &e){ +// disconnect(); + throw; + } + } + + string NitrokeyManager::get_status_as_string() { + auto response = GetStatus::CommandTransaction::run(device); + return response.data().dissect(); + } + + string getFilledOTPCode(uint32_t code, bool use_8_digits){ + stringstream s; + s << std::right << std::setw(use_8_digits ? 8 : 6) << std::setfill('0') << code; + return s.str(); + } + + string NitrokeyManager::get_HOTP_code(uint8_t slot_number, const char *user_temporary_password) { + if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + + if (is_authorization_command_supported()){ + auto gh = get_payload<GetHOTP>(); + gh.slot_number = get_internal_slot_number_for_hotp(slot_number); + if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen + authorize_packet<GetHOTP, UserAuthorize>(gh, user_temporary_password, device); + } + auto resp = GetHOTP::CommandTransaction::run(device, gh); + return getFilledOTPCode(resp.data().code, resp.data().use_8_digits); + } else { + auto gh = get_payload<stick10_08::GetHOTP>(); + gh.slot_number = get_internal_slot_number_for_hotp(slot_number); + if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0) { + strcpyT(gh.temporary_user_password, user_temporary_password); + } + auto resp = stick10_08::GetHOTP::CommandTransaction::run(device, gh); + return getFilledOTPCode(resp.data().code, resp.data().use_8_digits); + } + return ""; + } + + 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); } + uint8_t NitrokeyManager::get_internal_slot_number_for_hotp(uint8_t slot_number) const { return (uint8_t) (0x10 + slot_number); } + + + + string NitrokeyManager::get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, + uint8_t last_interval, + const char *user_temporary_password) { + if(!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + slot_number = get_internal_slot_number_for_totp(slot_number); + + if (is_authorization_command_supported()){ + auto gt = get_payload<GetTOTP>(); + gt.slot_number = slot_number; + gt.challenge = challenge; + gt.last_interval = last_interval; + gt.last_totp_time = last_totp_time; + + if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen + authorize_packet<GetTOTP, UserAuthorize>(gt, user_temporary_password, device); + } + auto resp = GetTOTP::CommandTransaction::run(device, gt); + return getFilledOTPCode(resp.data().code, resp.data().use_8_digits); + } else { + auto gt = get_payload<stick10_08::GetTOTP>(); + strcpyT(gt.temporary_user_password, user_temporary_password); + gt.slot_number = slot_number; + auto resp = stick10_08::GetTOTP::CommandTransaction::run(device, gt); + return getFilledOTPCode(resp.data().code, resp.data().use_8_digits); + } + return ""; + } + + bool NitrokeyManager::erase_slot(uint8_t slot_number, const char *temporary_password) { + if (is_authorization_command_supported()){ + auto p = get_payload<EraseSlot>(); + p.slot_number = slot_number; + authorize_packet<EraseSlot, Authorize>(p, temporary_password, device); + auto resp = EraseSlot::CommandTransaction::run(device,p); + } else { + auto p = get_payload<stick10_08::EraseSlot>(); + p.slot_number = slot_number; + strcpyT(p.temporary_admin_password, temporary_password); + auto resp = stick10_08::EraseSlot::CommandTransaction::run(device,p); + } + return true; + } + + bool NitrokeyManager::erase_hotp_slot(uint8_t slot_number, const char *temporary_password) { + if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + slot_number = get_internal_slot_number_for_hotp(slot_number); + return erase_slot(slot_number, temporary_password); + } + + bool NitrokeyManager::erase_totp_slot(uint8_t slot_number, const char *temporary_password) { + if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + slot_number = get_internal_slot_number_for_totp(slot_number); + return erase_slot(slot_number, temporary_password); + } + + template <typename T, typename U> + void vector_copy_ranged(T& dest, std::vector<U> &vec, size_t begin, size_t elements_to_copy){ + const size_t d_size = sizeof(dest); + if(d_size < elements_to_copy){ + throw TargetBufferSmallerThanSource(elements_to_copy, d_size); + } + std::fill(dest, dest+d_size, 0); + std::copy(vec.begin() + begin, vec.begin() +begin + elements_to_copy, dest); + } + + template <typename T, typename U> + void vector_copy(T& dest, std::vector<U> &vec){ + const size_t d_size = sizeof(dest); + if(d_size < vec.size()){ + throw TargetBufferSmallerThanSource(vec.size(), d_size); + } + std::fill(dest, dest+d_size, 0); + std::copy(vec.begin(), vec.end(), dest); + } + + bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { + if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + + int internal_slot_number = get_internal_slot_number_for_hotp(slot_number); + if (is_authorization_command_supported()){ + write_HOTP_slot_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } else { + write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } + return true; + } + + void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, + uint64_t hotp_counter, bool use_8_digits, bool use_enter, + bool use_tokenID, const char *token_ID, const char *temporary_password) { + auto payload = get_payload<WriteToHOTPSlot>(); + payload.slot_number = slot_number; + auto secret_bin = misc::hex_string_to_byte(secret); + vector_copy(payload.slot_secret, secret_bin); + strcpyT(payload.slot_name, slot_name); + strcpyT(payload.slot_token_id, token_ID); + switch (device->get_device_model() ){ + case DeviceModel::PRO: { + payload.slot_counter = hotp_counter; + break; + } + case DeviceModel::STORAGE: { + string counter = to_string(hotp_counter); + strcpyT(payload.slot_counter_s, counter.c_str()); + break; + } + default: + LOG(string(__FILE__) + to_string(__LINE__) + + string(__FUNCTION__) + string(" Unhandled device model for HOTP") + , Loglevel::DEBUG); + break; + } + payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; + + authorize_packet<WriteToHOTPSlot, Authorize>(payload, temporary_password, device); + + auto resp = WriteToHOTPSlot::CommandTransaction::run(device, payload); + } + + bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { + if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + int internal_slot_number = get_internal_slot_number_for_totp(slot_number); + + if (is_authorization_command_supported()){ + write_TOTP_slot_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } else { + write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } + + return true; + } + + void NitrokeyManager::write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name, + const char *secret, + uint64_t counter_or_interval, bool use_8_digits, bool use_enter, + bool use_tokenID, const char *token_ID, + const char *temporary_password) const { + + auto payload2 = get_payload<stick10_08::SendOTPData>(); + strcpyT(payload2.temporary_admin_password, temporary_password); + strcpyT(payload2.data, slot_name); + payload2.setTypeName(); + stick10_08::SendOTPData::CommandTransaction::run(device, payload2); + + payload2.setTypeSecret(); + payload2.id = 0; + auto secret_bin = misc::hex_string_to_byte(secret); + auto remaining_secret_length = secret_bin.size(); + const auto maximum_OTP_secret_size = 40; + if(remaining_secret_length > maximum_OTP_secret_size){ + throw TargetBufferSmallerThanSource(remaining_secret_length, maximum_OTP_secret_size); + } + + while (remaining_secret_length>0){ + const auto bytesToCopy = std::min(sizeof(payload2.data), remaining_secret_length); + const auto start = secret_bin.size() - remaining_secret_length; + memset(payload2.data, 0, sizeof(payload2.data)); + vector_copy_ranged(payload2.data, secret_bin, start, bytesToCopy); + stick10_08::SendOTPData::CommandTransaction::run(device, payload2); + remaining_secret_length -= bytesToCopy; + payload2.id++; + } + + auto payload = get_payload<stick10_08::WriteToOTPSlot>(); + strcpyT(payload.temporary_admin_password, temporary_password); + strcpyT(payload.slot_token_id, token_ID); + payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; + payload.slot_counter_or_interval = counter_or_interval; + payload.slot_number = internal_slot_number; + stick10_08::WriteToOTPSlot::CommandTransaction::run(device, payload); + } + + void NitrokeyManager::write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, + uint16_t time_window, bool use_8_digits, bool use_enter, + bool use_tokenID, const char *token_ID, const char *temporary_password) { + auto payload = get_payload<WriteToTOTPSlot>(); + payload.slot_number = slot_number; + auto secret_bin = misc::hex_string_to_byte(secret); + vector_copy(payload.slot_secret, secret_bin); + strcpyT(payload.slot_name, slot_name); + strcpyT(payload.slot_token_id, token_ID); + payload.slot_interval = time_window; //FIXME naming + payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; + + authorize_packet<WriteToTOTPSlot, Authorize>(payload, temporary_password, device); + + auto resp = WriteToTOTPSlot::CommandTransaction::run(device, payload); + } + + char * NitrokeyManager::get_totp_slot_name(uint8_t slot_number) { + if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + slot_number = get_internal_slot_number_for_totp(slot_number); + return get_slot_name(slot_number); + } + char * NitrokeyManager::get_hotp_slot_name(uint8_t slot_number) { + if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + slot_number = get_internal_slot_number_for_hotp(slot_number); + return get_slot_name(slot_number); + } + + static const int max_string_field_length = 2*1024; //storage's status string is ~1k + + char * NitrokeyManager::get_slot_name(uint8_t slot_number) { + auto payload = get_payload<GetSlotName>(); + payload.slot_number = slot_number; + auto resp = GetSlotName::CommandTransaction::run(device, payload); + return strndup((const char *) resp.data().slot_name, max_string_field_length); + } + + bool NitrokeyManager::first_authenticate(const char *pin, const char *temporary_password) { + auto authreq = get_payload<FirstAuthenticate>(); + strcpyT(authreq.card_password, pin); + strcpyT(authreq.temporary_password, temporary_password); + FirstAuthenticate::CommandTransaction::run(device, authreq); + return true; + } + + bool NitrokeyManager::set_time(uint64_t time) { + auto p = get_payload<SetTime>(); + p.reset = 1; + p.time = time; + SetTime::CommandTransaction::run(device, p); + return false; + } + + void NitrokeyManager::set_time_soft(uint64_t time) { + auto p = get_payload<SetTime>(); + p.reset = 0; + p.time = time; + SetTime::CommandTransaction::run(device, p); + } + + bool NitrokeyManager::get_time(uint64_t time) { + set_time_soft(time); + return true; + } + + void NitrokeyManager::change_user_PIN(const char *current_PIN, const char *new_PIN) { + change_PIN_general<ChangeUserPin, PasswordKind::User>(current_PIN, new_PIN); + } + + void NitrokeyManager::change_admin_PIN(const char *current_PIN, const char *new_PIN) { + change_PIN_general<ChangeAdminPin, PasswordKind::Admin>(current_PIN, new_PIN); + } + + template <typename ProCommand, PasswordKind StoKind> + void NitrokeyManager::change_PIN_general(const char *current_PIN, const char *new_PIN) { + switch (device->get_device_model()){ + case DeviceModel::PRO: + { + auto p = get_payload<ProCommand>(); + strcpyT(p.old_pin, current_PIN); + strcpyT(p.new_pin, new_PIN); + ProCommand::CommandTransaction::run(device, p); + } + break; + //in Storage change admin/user pin is divided to two commands with 20 chars field len + case DeviceModel::STORAGE: + { + auto p = get_payload<ChangeAdminUserPin20Current>(); + strcpyT(p.password, current_PIN); + p.set_kind(StoKind); + auto p2 = get_payload<ChangeAdminUserPin20New>(); + strcpyT(p2.password, new_PIN); + p2.set_kind(StoKind); + ChangeAdminUserPin20Current::CommandTransaction::run(device, p); + ChangeAdminUserPin20New::CommandTransaction::run(device, p2); + } + break; + } + + } + + void NitrokeyManager::enable_password_safe(const char *user_pin) { + //The following command will cancel enabling PWS if it is not supported + auto a = get_payload<IsAESSupported>(); + strcpyT(a.user_password, user_pin); + IsAESSupported::CommandTransaction::run(device, a); + + auto p = get_payload<EnablePasswordSafe>(); + strcpyT(p.user_password, user_pin); + EnablePasswordSafe::CommandTransaction::run(device, p); + } + + vector <uint8_t> NitrokeyManager::get_password_safe_slot_status() { + auto responsePayload = GetPasswordSafeSlotStatus::CommandTransaction::run(device); + vector<uint8_t> v = vector<uint8_t>(responsePayload.data().password_safe_status, + responsePayload.data().password_safe_status + + sizeof(responsePayload.data().password_safe_status)); + return v; + } + + uint8_t NitrokeyManager::get_user_retry_count() { + if(device->get_device_model() == DeviceModel::STORAGE){ + stick20::GetDeviceStatus::CommandTransaction::run(device); + } + auto response = GetUserPasswordRetryCount::CommandTransaction::run(device); + return response.data().password_retry_count; + } + + uint8_t NitrokeyManager::get_admin_retry_count() { + if(device->get_device_model() == DeviceModel::STORAGE){ + stick20::GetDeviceStatus::CommandTransaction::run(device); + } + auto response = GetPasswordRetryCount::CommandTransaction::run(device); + return response.data().password_retry_count; + } + + void NitrokeyManager::lock_device() { + LockDevice::CommandTransaction::run(device); + } + + char * NitrokeyManager::get_password_safe_slot_name(uint8_t slot_number) { + if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number); + auto p = get_payload<GetPasswordSafeSlotName>(); + p.slot_number = slot_number; + auto response = GetPasswordSafeSlotName::CommandTransaction::run(device, p); + return strndup((const char *) response.data().slot_name, max_string_field_length); + } + + bool NitrokeyManager::is_valid_password_safe_slot_number(uint8_t slot_number) const { return slot_number < 16; } + + char * NitrokeyManager::get_password_safe_slot_login(uint8_t slot_number) { + if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number); + auto p = get_payload<GetPasswordSafeSlotLogin>(); + p.slot_number = slot_number; + auto response = GetPasswordSafeSlotLogin::CommandTransaction::run(device, p); + return strndup((const char *) response.data().slot_login, max_string_field_length); + } + + char * NitrokeyManager::get_password_safe_slot_password(uint8_t slot_number) { + if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number); + auto p = get_payload<GetPasswordSafeSlotPassword>(); + p.slot_number = slot_number; + auto response = GetPasswordSafeSlotPassword::CommandTransaction::run(device, p); + return strndup((const char *) response.data().slot_password, max_string_field_length); //FIXME use secure way + } + + void NitrokeyManager::write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login, + const char *slot_password) { + if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number); + auto p = get_payload<SetPasswordSafeSlotData>(); + p.slot_number = slot_number; + strcpyT(p.slot_name, slot_name); + strcpyT(p.slot_password, slot_password); + SetPasswordSafeSlotData::CommandTransaction::run(device, p); + + auto p2 = get_payload<SetPasswordSafeSlotData2>(); + p2.slot_number = slot_number; + strcpyT(p2.slot_login_name, slot_login); + SetPasswordSafeSlotData2::CommandTransaction::run(device, p2); + } + + void NitrokeyManager::erase_password_safe_slot(uint8_t slot_number) { + if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number); + auto p = get_payload<ErasePasswordSafeSlot>(); + p.slot_number = slot_number; + ErasePasswordSafeSlot::CommandTransaction::run(device, p); + } + + void NitrokeyManager::user_authenticate(const char *user_password, const char *temporary_password) { + auto p = get_payload<UserAuthenticate>(); + strcpyT(p.card_password, user_password); + strcpyT(p.temporary_password, temporary_password); + UserAuthenticate::CommandTransaction::run(device, p); + } + + void NitrokeyManager::build_aes_key(const char *admin_password) { + switch (device->get_device_model()) { + case DeviceModel::PRO: { + auto p = get_payload<BuildAESKey>(); + strcpyT(p.admin_password, admin_password); + BuildAESKey::CommandTransaction::run(device, p); + break; + } + case DeviceModel::STORAGE : { + auto p = get_payload<stick20::CreateNewKeys>(); + strcpyT(p.password, admin_password); + p.set_defaults(); + stick20::CreateNewKeys::CommandTransaction::run(device, p); + break; + } + } + } + + void NitrokeyManager::factory_reset(const char *admin_password) { + auto p = get_payload<FactoryReset>(); + strcpyT(p.admin_password, admin_password); + FactoryReset::CommandTransaction::run(device, p); + } + + void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) { + switch (device->get_device_model()){ + case DeviceModel::PRO: { + auto p = get_payload<stick10::UnlockUserPassword>(); + strcpyT(p.admin_password, admin_password); + strcpyT(p.user_new_password, new_user_password); + stick10::UnlockUserPassword::CommandTransaction::run(device, p); + break; + } + case DeviceModel::STORAGE : { + auto p2 = get_payload<ChangeAdminUserPin20Current>(); + p2.set_defaults(); + strcpyT(p2.password, admin_password); + ChangeAdminUserPin20Current::CommandTransaction::run(device, p2); + auto p3 = get_payload<stick20::UnlockUserPin>(); + p3.set_defaults(); + strcpyT(p3.password, new_user_password); + stick20::UnlockUserPin::CommandTransaction::run(device, p3); + break; + } + } + } + + + void NitrokeyManager::write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password, + bool delete_user_password, const char *admin_temporary_password) { + auto p = get_payload<stick10_08::WriteGeneralConfig>(); + p.numlock = numlock; + p.capslock = capslock; + p.scrolllock = scrolllock; + p.enable_user_password = static_cast<uint8_t>(enable_user_password ? 1 : 0); + p.delete_user_password = static_cast<uint8_t>(delete_user_password ? 1 : 0); + if (is_authorization_command_supported()){ + authorize_packet<stick10_08::WriteGeneralConfig, Authorize>(p, admin_temporary_password, device); + } else { + strcpyT(p.temporary_admin_password, admin_temporary_password); + } + stick10_08::WriteGeneralConfig::CommandTransaction::run(device, p); + } + + vector<uint8_t> NitrokeyManager::read_config() { + auto responsePayload = GetStatus::CommandTransaction::run(device); + vector<uint8_t> v = vector<uint8_t>(responsePayload.data().general_config, + responsePayload.data().general_config+sizeof(responsePayload.data().general_config)); + return v; + } + + bool NitrokeyManager::is_authorization_command_supported(){ + //authorization command is supported for versions equal or below: + auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ + {DeviceModel::PRO, 7}, + {DeviceModel::STORAGE, 999}, + }); + 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: + auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ + {DeviceModel::PRO, 8}, + {DeviceModel::STORAGE, 999}, + }); + return get_minor_firmware_version() >= m[device->get_device_model()]; + } + + DeviceModel NitrokeyManager::get_connected_device_model() const{ + if (device == nullptr){ + throw DeviceNotConnected("device not connected"); + } + return device->get_device_model(); + } + + bool NitrokeyManager::is_smartcard_in_use(){ + try{ + stick20::CheckSmartcardUsage::CommandTransaction::run(device); + } + catch(const CommandFailedException & e){ + return e.reason_smartcard_busy(); + } + return false; + } + + int NitrokeyManager::get_minor_firmware_version(){ + switch(device->get_device_model()){ + case DeviceModel::PRO:{ + auto status_p = GetStatus::CommandTransaction::run(device); + return status_p.data().firmware_version_st.minor; //7 or 8 + } + case DeviceModel::STORAGE:{ + auto status = stick20::GetDeviceStatus::CommandTransaction::run(device); + auto test_firmware = status.data().versionInfo.build_iteration != 0; + if (test_firmware) + LOG("Development firmware detected. Increasing minor version number.", nitrokey::log::Loglevel::WARNING); + return status.data().versionInfo.minor + (test_firmware? 1 : 0); + } + } + return 0; + } + int NitrokeyManager::get_major_firmware_version(){ + switch(device->get_device_model()){ + case DeviceModel::PRO:{ + auto status_p = GetStatus::CommandTransaction::run(device); + return status_p.data().firmware_version_st.major; //0 + } + case DeviceModel::STORAGE:{ + auto status = stick20::GetDeviceStatus::CommandTransaction::run(device); + return status.data().versionInfo.major; + } + } + return 0; + } + + bool NitrokeyManager::is_AES_supported(const char *user_password) { + auto a = get_payload<IsAESSupported>(); + strcpyT(a.user_password, user_password); + IsAESSupported::CommandTransaction::run(device, a); + return true; + } + + //storage commands + + void NitrokeyManager::send_startup(uint64_t seconds_from_epoch){ + auto p = get_payload<stick20::SendStartup>(); +// p.set_defaults(); //set current time + p.localtime = seconds_from_epoch; + stick20::SendStartup::CommandTransaction::run(device, p); + } + + void NitrokeyManager::unlock_encrypted_volume(const char* user_pin){ + misc::execute_password_command<stick20::EnableEncryptedPartition>(device, user_pin); + } + + void NitrokeyManager::unlock_hidden_volume(const char* hidden_volume_password) { + misc::execute_password_command<stick20::EnableHiddenEncryptedPartition>(device, hidden_volume_password); + } + + void NitrokeyManager::set_encrypted_volume_read_only(const char* admin_pin) { + misc::execute_password_command<stick20::SetEncryptedVolumeReadOnly>(device, admin_pin); + } + + void NitrokeyManager::set_encrypted_volume_read_write(const char* admin_pin) { + misc::execute_password_command<stick20::SetEncryptedVolumeReadWrite>(device, admin_pin); + } + + //TODO check is encrypted volume unlocked before execution + //if not return library exception + void NitrokeyManager::create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, + const char *hidden_volume_password) { + auto p = get_payload<stick20::SetupHiddenVolume>(); + p.SlotNr_u8 = slot_nr; + p.StartBlockPercent_u8 = start_percent; + p.EndBlockPercent_u8 = end_percent; + strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password); + stick20::SetupHiddenVolume::CommandTransaction::run(device, p); + } + + void NitrokeyManager::set_unencrypted_read_only_admin(const char* admin_pin) { + //from v0.49, v0.52+ it needs Admin PIN + if (set_unencrypted_volume_rorw_pin_type_user()){ + LOG("set_unencrypted_read_only_admin is not supported for this version of Storage device. " + "Please update firmware to v0.52+. Doing nothing.", nitrokey::log::Loglevel::WARNING); + return; + } + misc::execute_password_command<stick20::SetUnencryptedVolumeReadOnlyAdmin>(device, admin_pin); + } + + void NitrokeyManager::set_unencrypted_read_only(const char *user_pin) { + //until v0.48 (incl. v0.50 and v0.51) User PIN was sufficient + LOG("set_unencrypted_read_only is deprecated. Use set_unencrypted_read_only_admin instead.", + nitrokey::log::Loglevel::WARNING); + if (!set_unencrypted_volume_rorw_pin_type_user()){ + LOG("set_unencrypted_read_only is not supported for this version of Storage device. Doing nothing.", + nitrokey::log::Loglevel::WARNING); + return; + } + misc::execute_password_command<stick20::SendSetReadonlyToUncryptedVolume>(device, user_pin); + } + + void NitrokeyManager::set_unencrypted_read_write_admin(const char* admin_pin) { + //from v0.49, v0.52+ it needs Admin PIN + if (set_unencrypted_volume_rorw_pin_type_user()){ + LOG("set_unencrypted_read_write_admin is not supported for this version of Storage device. " + "Please update firmware to v0.52+. Doing nothing.", nitrokey::log::Loglevel::WARNING); + return; + } + misc::execute_password_command<stick20::SetUnencryptedVolumeReadWriteAdmin>(device, admin_pin); + } + + void NitrokeyManager::set_unencrypted_read_write(const char *user_pin) { + //until v0.48 (incl. v0.50 and v0.51) User PIN was sufficient + LOG("set_unencrypted_read_write is deprecated. Use set_unencrypted_read_write_admin instead.", + nitrokey::log::Loglevel::WARNING); + if (!set_unencrypted_volume_rorw_pin_type_user()){ + LOG("set_unencrypted_read_write is not supported for this version of Storage device. Doing nothing.", + nitrokey::log::Loglevel::WARNING); + return; + } + misc::execute_password_command<stick20::SendSetReadwriteToUncryptedVolume>(device, user_pin); + } + + bool NitrokeyManager::set_unencrypted_volume_rorw_pin_type_user(){ + auto minor_firmware_version = get_minor_firmware_version(); + return minor_firmware_version <= 48 || minor_firmware_version == 50 || minor_firmware_version == 51; + } + + void NitrokeyManager::export_firmware(const char* admin_pin) { + misc::execute_password_command<stick20::ExportFirmware>(device, admin_pin); + } + + void NitrokeyManager::enable_firmware_update(const char* firmware_pin) { + misc::execute_password_command<stick20::EnableFirmwareUpdate>(device, firmware_pin); + } + + void NitrokeyManager::clear_new_sd_card_warning(const char* admin_pin) { + misc::execute_password_command<stick20::SendClearNewSdCardFound>(device, admin_pin); + } + + void NitrokeyManager::fill_SD_card_with_random_data(const char* admin_pin) { + auto p = get_payload<stick20::FillSDCardWithRandomChars>(); + p.set_defaults(); + strcpyT(p.admin_pin, admin_pin); + stick20::FillSDCardWithRandomChars::CommandTransaction::run(device, p); + } + + void NitrokeyManager::change_update_password(const char* current_update_password, const char* new_update_password) { + auto p = get_payload<stick20::ChangeUpdatePassword>(); + strcpyT(p.current_update_password, current_update_password); + strcpyT(p.new_update_password, new_update_password); + stick20::ChangeUpdatePassword::CommandTransaction::run(device, p); + } + + char * NitrokeyManager::get_status_storage_as_string(){ + auto p = stick20::GetDeviceStatus::CommandTransaction::run(device); + return strndup(p.data().dissect().c_str(), max_string_field_length); + } + + stick20::DeviceConfigurationResponsePacket::ResponsePayload NitrokeyManager::get_status_storage(){ + auto p = stick20::GetDeviceStatus::CommandTransaction::run(device); + return p.data(); + } + + char * NitrokeyManager::get_SD_usage_data_as_string(){ + auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(device); + return strndup(p.data().dissect().c_str(), max_string_field_length); + } + + std::pair<uint8_t,uint8_t> NitrokeyManager::get_SD_usage_data(){ + auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(device); + return std::make_pair(p.data().WriteLevelMin, p.data().WriteLevelMax); + } + + int NitrokeyManager::get_progress_bar_value(){ + try{ + stick20::GetDeviceStatus::CommandTransaction::run(device); + return -1; + } + catch (LongOperationInProgressException &e){ + return e.progress_bar_value; + } + } + + string NitrokeyManager::get_TOTP_code(uint8_t slot_number, const char *user_temporary_password) { + return get_TOTP_code(slot_number, 0, 0, 0, user_temporary_password); + } + + stick10::ReadSlot::ResponsePayload NitrokeyManager::get_OTP_slot_data(const uint8_t slot_number) { + auto p = get_payload<stick10::ReadSlot>(); + p.slot_number = slot_number; + auto data = stick10::ReadSlot::CommandTransaction::run(device, p); + return data.data(); + } + + stick10::ReadSlot::ResponsePayload NitrokeyManager::get_TOTP_slot_data(const uint8_t slot_number) { + return get_OTP_slot_data(get_internal_slot_number_for_totp(slot_number)); + } + + 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; + } + + void NitrokeyManager::lock_encrypted_volume() { + misc::execute_password_command<stick20::DisableEncryptedPartition>(device, ""); + } + + void NitrokeyManager::lock_hidden_volume() { + misc::execute_password_command<stick20::DisableHiddenEncryptedPartition>(device, ""); + } + + uint8_t NitrokeyManager::get_SD_card_size() { + auto data = stick20::ProductionTest::CommandTransaction::run(device); + return data.data().SD_Card_Size_u8; + } + + const string NitrokeyManager::get_current_device_id() const { + return current_device_id; + } + + void NitrokeyManager::wink(){ + stick20::Wink::CommandTransaction::run(device); + }; + + stick20::ProductionTest::ResponsePayload NitrokeyManager::production_info(){ + auto data = stick20::ProductionTest::CommandTransaction::run(device); + return data.data(); + }; + +} diff --git a/nitrokey-sys/libnitrokey-v3.4.1/README.md b/nitrokey-sys/libnitrokey-v3.4.1/README.md new file mode 100644 index 0000000..81b367a --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/README.md @@ -0,0 +1,210 @@ +[![Build Status](https://travis-ci.org/Nitrokey/libnitrokey.svg?branch=master)](https://travis-ci.org/Nitrokey/libnitrokey) +[![Waffle.io - Columns and their card count](https://badge.waffle.io/Nitrokey/libnitrokey.svg?columns=ready,in%20progress,test,waiting%20for%20feedback)](https://waffle.io/Nitrokey/libnitrokey) + +# 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). + +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). + +libnitrokey is developed and tested with the latest compilers: g++ 6.2, clang 3.8. We use Travis CI to test builds also on g++ 5.4 and under OSX compilers starting up from xcode 8.2 environment. + +## Getting sources +This repository uses `git submodules`. +To clone please use git's `--recursive` option like in: +```bash +git clone --recursive https://github.com/Nitrokey/libnitrokey.git +``` +or for already cloned repository: +```bash +git clone https://github.com/Nitrokey/libnitrokey.git +cd libnitrokey +git submodule update --init --recursive +``` + +## Dependencies +Following libraries are needed to use libnitrokey on Linux (names of the packages on Ubuntu): +- libhidapi-dev [(HID API)](http://www.signal11.us/oss/hidapi/) +- libusb-1.0-0-dev + + +## Compilation +libnitrokey uses CMake as its main build system. As a secondary option it offers building through Qt's qMake. +### Qt +A Qt's .pro project file is provided for direct compilation and for inclusion to other projects. +Using it directly is not recommended due to lack of dependencies check and not implemented library versioning. +Compilation is tested with Qt 5.6 and greater. + +Quick start example: +```bash +mkdir -p build +cd build +qmake .. +make -j2 +``` + +### Windows MS 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 + +It is possible too to use CMake GUI directly with its settings editor. + +### CMake +To compile please run following sequence of commands: +```bash +# assuming current dir is ./libnitrokey/ +mkdir -p build +cd build +cmake .. <OPTIONS> +make -j2 +``` + +By default (with empty `<OPTIONS>` string) this will create in `build/` directory a shared library (.so, .dll or .dynlib). If you wish to build static version you can use as `<OPTIONS>` string `-DBUILD_SHARED_LIBS=OFF`. + +All options could be listed with `cmake .. -L` or instead `cmake` a `ccmake ..` tool could be used for configuration (where `..` is the path to directory with `CMakeLists.txt` file). `ccmake` shows also description of the build parameters. + +If you have trouble compiling or running the library you can check [.travis.yml](.travis.yml) file for configuration details. This file is used by Travis CI service to make test builds on OSX and Ubuntu 14.04. + +Other build options (all take either `ON` or `OFF`): +* ADD_ASAN - add tests for memory leaks and out-of-bounds access +* ADD_TSAN - add tests for threads race, needs USE_CLANG +* COMPILE_TESTS - compile C++ tests +* COMPILE_OFFLINE_TESTS - compile C++ tests, that do not require any device to be connected +* LOG_VOLATILE_DATA (default: OFF) - include secrets in log (PWS passwords, PINs etc) +* NO_LOG (default: OFF) - do not compile LOG statements - will make library smaller, but without any diagnostic messages + + + +# 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: +```bash +pip install --user cffi # for python 2.x +pip3 install cffi # for python 3.x +``` +Just import it, read the C API header and it is done! You have access to the library. Here is an example (in Python 2) printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python_bindings_example.py): +```python +#!/usr/bin/env python2 +import cffi + +ffi = cffi.FFI() +get_string = ffi.string + +def get_library(): + fp = 'NK_C_API.h' # path to C API header + + declarations = [] + with open(fp, 'r') as f: + declarations = f.readlines() + + cnt = 0 + a = iter(declarations) + for declaration in a: + if declaration.strip().startswith('NK_C_API'): + declaration = declaration.replace('NK_C_API', '').strip() + while ';' not in declaration: + declaration += (next(a)).strip() + # print(declaration) + ffi.cdef(declaration, override=True) + cnt +=1 + print('Imported {} declarations'.format(cnt)) + + + C = None + import os, sys + path_build = os.path.join(".", "build") + paths = [ + os.environ.get('LIBNK_PATH', None), + os.path.join(path_build,"libnitrokey.so"), + os.path.join(path_build,"libnitrokey.dylib"), + os.path.join(path_build,"libnitrokey.dll"), + os.path.join(path_build,"nitrokey.dll"), + ] + for p in paths: + if not p: continue + print("Trying " +p) + p = os.path.abspath(p) + if os.path.exists(p): + print("Found: "+p) + C = ffi.dlopen(p) + break + else: + print("File does not exist: " + p) + if not C: + print("No library file found") + sys.exit(1) + + return C + + +def get_hotp_code(lib, i): + return lib.NK_get_hotp_code(i) + + +libnitrokey = get_library() +libnitrokey.NK_set_debug(False) # do not show debug messages (log library only) + +hotp_slot_code = get_hotp_code(libnitrokey, 1) +print('Getting HOTP code from Nitrokey device: ') +print(hotp_slot_code) +libnitrokey.NK_logout() # disconnect device +``` + +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. + +# 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). + +## Python tests +libnitrokey has a great suite of tests written in Python 3 under the path: `unittest/test_*.py`: +* `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 +``` +To run them please execute: +```bash +# 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 + +``` +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`. + + +The documentation of how it works could be found in nitrokey-app project's README on Github: +[Nitrokey-app - internals](https://github.com/Nitrokey/nitrokey-app/blob/master/README.md#internals). + +To peek/debug communication with device running nitrokey-app (0.x branch) in debug mode (`-d` switch) and checking the logs +(right click on tray icon and then 'Debug') might be helpful. Latest Nitrokey App (1.x branch) uses libnitrokey to communicate with device. Once run with `--dl 3` (3 or higher; range 0-5) it will print all communication to the console. Additionally crosschecking with +firmware code should show how things works: +[report_protocol.c](https://github.com/Nitrokey/nitrokey-pro-firmware/blob/master/src/keyboard/report_protocol.c) +(for Nitrokey Pro, for Storage similarly). + +# 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, +* Fix compilation warnings. + +Other tasks might be listed either in [TODO](TODO) file or on project's issues page. + +# License +This project is licensed under LGPL version 3. License text could be found under [LICENSE](LICENSE) file. + +# Roadmap +To check what issues will be fixed and when please check [milestones](https://github.com/Nitrokey/libnitrokey/milestones) page. diff --git a/nitrokey-sys/libnitrokey-v3.4.1/command_id.cc b/nitrokey-sys/libnitrokey-v3.4.1/command_id.cc new file mode 100644 index 0000000..a6c2a28 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/command_id.cc @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include <assert.h> +#include "command_id.h" + +namespace nitrokey { +namespace proto { + +const char *commandid_to_string(CommandID id) { + switch (id) { + case CommandID::GET_STATUS: + return "GET_STATUS"; + case CommandID::WRITE_TO_SLOT: + return "WRITE_TO_SLOT"; + case CommandID::READ_SLOT_NAME: + return "READ_SLOT_NAME"; + case CommandID::READ_SLOT: + return "READ_SLOT"; + case CommandID::GET_CODE: + return "GET_CODE"; + case CommandID::WRITE_CONFIG: + return "WRITE_CONFIG"; + case CommandID::ERASE_SLOT: + return "ERASE_SLOT"; + case CommandID::FIRST_AUTHENTICATE: + return "FIRST_AUTHENTICATE"; + case CommandID::AUTHORIZE: + return "AUTHORIZE"; + case CommandID::GET_PASSWORD_RETRY_COUNT: + return "GET_PASSWORD_RETRY_COUNT"; + case CommandID::CLEAR_WARNING: + return "CLEAR_WARNING"; + case CommandID::SET_TIME: + return "SET_TIME"; + case CommandID::TEST_COUNTER: + return "TEST_COUNTER"; + case CommandID::TEST_TIME: + return "TEST_TIME"; + case CommandID::USER_AUTHENTICATE: + return "USER_AUTHENTICATE"; + case CommandID::GET_USER_PASSWORD_RETRY_COUNT: + return "GET_USER_PASSWORD_RETRY_COUNT"; + case CommandID::USER_AUTHORIZE: + return "USER_AUTHORIZE"; + case CommandID::UNLOCK_USER_PASSWORD: + return "UNLOCK_USER_PASSWORD"; + case CommandID::LOCK_DEVICE: + return "LOCK_DEVICE"; + case CommandID::FACTORY_RESET: + return "FACTORY_RESET"; + case CommandID::CHANGE_USER_PIN: + return "CHANGE_USER_PIN"; + case CommandID::CHANGE_ADMIN_PIN: + return "CHANGE_ADMIN_PIN"; + + case CommandID::ENABLE_CRYPTED_PARI: + return "ENABLE_CRYPTED_PARI"; + case CommandID::DISABLE_CRYPTED_PARI: + return "DISABLE_CRYPTED_PARI"; + case CommandID::ENABLE_HIDDEN_CRYPTED_PARI: + return "ENABLE_HIDDEN_CRYPTED_PARI"; + case CommandID::DISABLE_HIDDEN_CRYPTED_PARI: + return "DISABLE_HIDDEN_CRYPTED_PARI"; + case CommandID::ENABLE_FIRMWARE_UPDATE: + return "ENABLE_FIRMWARE_UPDATE"; + case CommandID::EXPORT_FIRMWARE_TO_FILE: + return "EXPORT_FIRMWARE_TO_FILE"; + case CommandID::GENERATE_NEW_KEYS: + return "GENERATE_NEW_KEYS"; + case CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS: + return "FILL_SD_CARD_WITH_RANDOM_CHARS"; + + case CommandID::WRITE_STATUS_DATA: + return "WRITE_STATUS_DATA"; + case CommandID::ENABLE_READONLY_UNCRYPTED_LUN: + return "ENABLE_READONLY_UNCRYPTED_LUN"; + case CommandID::ENABLE_READWRITE_UNCRYPTED_LUN: + return "ENABLE_READWRITE_UNCRYPTED_LUN"; + + case CommandID::SEND_PASSWORD_MATRIX: + return "SEND_PASSWORD_MATRIX"; + case CommandID::SEND_PASSWORD_MATRIX_PINDATA: + return "SEND_PASSWORD_MATRIX_PINDATA"; + case CommandID::SEND_PASSWORD_MATRIX_SETUP: + return "SEND_PASSWORD_MATRIX_SETUP"; + + case CommandID::GET_DEVICE_STATUS: + return "GET_DEVICE_STATUS"; + case CommandID::SEND_DEVICE_STATUS: + return "SEND_DEVICE_STATUS"; + + case CommandID::SEND_HIDDEN_VOLUME_PASSWORD: + return "SEND_HIDDEN_VOLUME_PASSWORD"; + case CommandID::SEND_HIDDEN_VOLUME_SETUP: + return "SEND_HIDDEN_VOLUME_SETUP"; + case CommandID::SEND_PASSWORD: + return "SEND_PASSWORD"; + case CommandID::SEND_NEW_PASSWORD: + return "SEND_NEW_PASSWORD"; + case CommandID::CLEAR_NEW_SD_CARD_FOUND: + return "CLEAR_NEW_SD_CARD_FOUND"; + + case CommandID::SEND_STARTUP: + return "SEND_STARTUP"; + case CommandID::SEND_CLEAR_STICK_KEYS_NOT_INITIATED: + return "SEND_CLEAR_STICK_KEYS_NOT_INITIATED"; + case CommandID::SEND_LOCK_STICK_HARDWARE: + return "SEND_LOCK_STICK_HARDWARE"; + + case CommandID::PRODUCTION_TEST: + return "PRODUCTION_TEST"; + case CommandID::SEND_DEBUG_DATA: + return "SEND_DEBUG_DATA"; + + case CommandID::CHANGE_UPDATE_PIN: + return "CHANGE_UPDATE_PIN"; + + case CommandID::ENABLE_ADMIN_READONLY_UNCRYPTED_LUN: + return "ENABLE_ADMIN_READONLY_UNCRYPTED_LUN"; + case CommandID::ENABLE_ADMIN_READWRITE_UNCRYPTED_LUN: + return "ENABLE_ADMIN_READWRITE_UNCRYPTED_LUN"; + case CommandID::ENABLE_ADMIN_READONLY_ENCRYPTED_LUN: + return "ENABLE_ADMIN_READONLY_ENCRYPTED_LUN"; + case CommandID::ENABLE_ADMIN_READWRITE_ENCRYPTED_LUN: + return "ENABLE_ADMIN_READWRITE_ENCRYPTED_LUN"; + case CommandID::CHECK_SMARTCARD_USAGE: + return "CHECK_SMARTCARD_USAGE"; + + case CommandID::GET_PW_SAFE_SLOT_STATUS: + return "GET_PW_SAFE_SLOT_STATUS"; + case CommandID::GET_PW_SAFE_SLOT_NAME: + return "GET_PW_SAFE_SLOT_NAME"; + case CommandID::GET_PW_SAFE_SLOT_PASSWORD: + return "GET_PW_SAFE_SLOT_PASSWORD"; + case CommandID::GET_PW_SAFE_SLOT_LOGINNAME: + return "GET_PW_SAFE_SLOT_LOGINNAME"; + case CommandID::SET_PW_SAFE_SLOT_DATA_1: + return "SET_PW_SAFE_SLOT_DATA_1"; + case CommandID::SET_PW_SAFE_SLOT_DATA_2: + return "SET_PW_SAFE_SLOT_DATA_2"; + case CommandID::PW_SAFE_ERASE_SLOT: + return "PW_SAFE_ERASE_SLOT"; + case CommandID::PW_SAFE_ENABLE: + return "PW_SAFE_ENABLE"; + case CommandID::PW_SAFE_INIT_KEY: + return "PW_SAFE_INIT_KEY"; + case CommandID::PW_SAFE_SEND_DATA: + return "PW_SAFE_SEND_DATA"; + case CommandID::SD_CARD_HIGH_WATERMARK: + return "SD_CARD_HIGH_WATERMARK"; + case CommandID::DETECT_SC_AES: + return "DETECT_SC_AES"; + case CommandID::NEW_AES_KEY: + return "NEW_AES_KEY"; + case CommandID::WRITE_TO_SLOT_2: + return "WRITE_TO_SLOT_2"; + case CommandID::SEND_OTP_DATA: + return "SEND_OTP_DATA"; + case CommandID::WINK: + return "WINK"; + } + return "UNKNOWN"; +} +} +} diff --git a/nitrokey-sys/libnitrokey-v3.4.1/device.cc b/nitrokey-sys/libnitrokey-v3.4.1/device.cc new file mode 100644 index 0000000..80e4b38 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/device.cc @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include <chrono> +#include <iostream> +#include <thread> +#include <cstddef> +#include <stdexcept> +#include "hidapi/hidapi.h" +#include "libnitrokey/misc.h" +#include "libnitrokey/device.h" +#include "libnitrokey/log.h" +#include <mutex> +#include "DeviceCommunicationExceptions.h" +#include "device.h" + +std::mutex mex_dev_com; + +using namespace nitrokey::device; +using namespace nitrokey::log; +using namespace std::chrono; + +std::atomic_int Device::instances_count{0}; +std::chrono::milliseconds Device::default_delay {0} ; + +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) + : + last_command_status(0), + m_vid(vid), + m_pid(pid), + m_model(model), + m_retry_sending_count(1), + m_retry_receiving_count(retry_receiving_count), + m_retry_timeout(retry_timeout), + m_send_receive_delay(send_receive_delay), + mp_devhandle(nullptr) +{ + instances_count++; +} + +bool Device::disconnect() { + //called in object's destructor + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + std::lock_guard<std::mutex> lock(mex_dev_com); + return _disconnect(); +} + +bool Device::_disconnect() { + LOG(std::string(__FUNCTION__) + std::string(m_model == DeviceModel::PRO ? "PRO" : "STORAGE"), Loglevel::DEBUG_L2); + LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2); + + if(mp_devhandle == nullptr) { + LOG(std::string("Disconnection: handle already freed: ") + std::to_string(mp_devhandle == nullptr) + " ("+m_path+")", Loglevel::DEBUG_L1); + return false; + } + + hid_close(mp_devhandle); + mp_devhandle = nullptr; +#ifndef __APPLE__ + if (instances_count == 1){ + LOG(std::string("Calling hid_exit"), Loglevel::DEBUG_L2); + hid_exit(); + } +#endif + return true; +} + +bool Device::connect() { + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + std::lock_guard<std::mutex> lock(mex_dev_com); + return _connect(); +} + +bool Device::_connect() { + LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2); + +// hid_init(); // done automatically on hid_open + if (m_path.empty()){ + mp_devhandle = hid_open(m_vid, m_pid, nullptr); + } else { + mp_devhandle = hid_open_path(m_path.c_str()); + } + const bool success = mp_devhandle != nullptr; + LOG(std::string("Connection success: ") + std::to_string(success) + " ("+m_path+")", Loglevel::DEBUG_L1); + return success; +} + +void Device::set_path(const std::string path){ + m_path = path; +} + +int Device::send(const void *packet) { + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + std::lock_guard<std::mutex> lock(mex_dev_com); + LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2); + + int send_feature_report = -1; + + for (int i = 0; i < 3 && send_feature_report < 0; ++i) { + if (mp_devhandle == nullptr) { + LOG(std::string("Connection fail") , Loglevel::DEBUG_L2); + throw DeviceNotConnected("Attempted HID send on an invalid descriptor."); + } + send_feature_report = hid_send_feature_report( + mp_devhandle, (const unsigned char *)(packet), HID_REPORT_SIZE); + if (send_feature_report < 0) _reconnect(); + //add thread sleep? + LOG(std::string("Sending attempt: ")+std::to_string(i+1) + " / 3" , Loglevel::DEBUG_L2); + } + return send_feature_report; +} + +int Device::recv(void *packet) { + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + std::lock_guard<std::mutex> lock(mex_dev_com); + LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2); + int status; + int retry_count = 0; + + for (;;) { + if (mp_devhandle == nullptr){ + LOG(std::string("Connection fail") , Loglevel::DEBUG_L2); + throw DeviceNotConnected("Attempted HID receive on an invalid descriptor."); + } + + status = (hid_get_feature_report(mp_devhandle, (unsigned char *)(packet), + HID_REPORT_SIZE)); + + auto pwherr = hid_error(mp_devhandle); + std::wstring wherr = (pwherr != nullptr) ? pwherr : L"No error message"; + std::string herr(wherr.begin(), wherr.end()); + LOG(std::string("libhid error message: ") + herr, + Loglevel::DEBUG_L2); + + if (status > 0) break; // success + if (retry_count++ >= m_retry_receiving_count) { + LOG( + "Maximum retry count reached: " + std::to_string(retry_count), + Loglevel::WARNING); + LOG( + std::string("Counter stats: ") + m_counters.get_as_string(), + Loglevel::DEBUG); + break; + } + _reconnect(); + LOG("Retrying... " + std::to_string(retry_count), + Loglevel::DEBUG); + std::this_thread::sleep_for(m_retry_timeout); + } + + return status; +} + +std::vector<std::string> Device::enumerate(){ + //TODO make static + auto pInfo = hid_enumerate(m_vid, m_pid); + auto pInfo_ = pInfo; + std::vector<std::string> res; + while (pInfo != nullptr){ + std::string a (pInfo->path); + res.push_back(a); + pInfo = pInfo->next; + } + + if (pInfo_ != nullptr){ + hid_free_enumeration(pInfo_); + } + + return res; +} + +bool Device::could_be_enumerated() { + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + std::lock_guard<std::mutex> lock(mex_dev_com); + if (mp_devhandle==nullptr){ + return false; + } +#ifndef __APPLE__ + auto pInfo = hid_enumerate(m_vid, m_pid); + if (pInfo != nullptr){ + hid_free_enumeration(pInfo); + return true; + } + return false; +#else +// alternative for OSX + unsigned char buf[1]; + return hid_read_timeout(mp_devhandle, buf, sizeof(buf), 20) != -1; +#endif +} + +void Device::show_stats() { + auto s = m_counters.get_as_string(); + LOG(s, Loglevel::DEBUG_L2); +} + +void Device::_reconnect() { + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + ++m_counters.low_level_reconnect; + _disconnect(); + _connect(); +} + +Device::~Device() { + show_stats(); + disconnect(); + instances_count--; +} + +void Device::set_default_device_speed(int delay) { + default_delay = std::chrono::duration<int, std::milli>(delay); +} + + +void Device::set_receiving_delay(const std::chrono::milliseconds delay){ + std::lock_guard<std::mutex> lock(mex_dev_com); + m_send_receive_delay = delay; +} + +void Device::set_retry_delay(const std::chrono::milliseconds delay){ + std::lock_guard<std::mutex> lock(mex_dev_com); + m_retry_timeout = delay; +} + +Stick10::Stick10(): + Device(0x20a0, 0x4108, DeviceModel::PRO, 100ms, 5, 100ms) + { + setDefaultDelay(); + } + + +Stick20::Stick20(): + Device(0x20a0, 0x4109, DeviceModel::STORAGE, 40ms, 55, 40ms) + { + setDefaultDelay(); + } + +#include <sstream> +#define p(x) ss << #x << " " << x << ", "; +std::string Device::ErrorCounters::get_as_string() { + std::stringstream ss; + p(total_comm_runs); + p(communication_successful); + ss << "("; + p(command_successful_recv); + p(command_result_not_equal_0_recv); + ss << "), "; + p(sends_executed); + p(recv_executed); + p(successful_storage_commands); + p(total_retries); + ss << "("; + p(busy); + p(busy_progressbar); + p(CRC_other_than_awaited); + p(wrong_CRC); + ss << "), "; + p(low_level_reconnect); + p(sending_error); + p(receiving_error); + return ss.str(); +} + +void Device::setDefaultDelay() { + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + + auto count = default_delay.count(); + if (count != 0){ + LOG("Setting default delay to " + std::to_string(count), Loglevel::DEBUG_L2); + m_retry_timeout = default_delay; + m_send_receive_delay = default_delay; + } +} + +#undef p diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/CommandFailedException.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/CommandFailedException.h new file mode 100644 index 0000000..32bd6b7 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/CommandFailedException.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LIBNITROKEY_COMMANDFAILEDEXCEPTION_H +#define LIBNITROKEY_COMMANDFAILEDEXCEPTION_H + +#include <exception> +#include <cstdint> +#include "log.h" +#include "command_id.h" + +using cs = nitrokey::proto::stick10::command_status; +using cs2 = nitrokey::proto::stick20::device_status; + +class CommandFailedException : public std::exception { +public: + const uint8_t last_command_id; + const uint8_t last_command_status; + + CommandFailedException(uint8_t last_command_id, uint8_t last_command_status) : + last_command_id(last_command_id), + last_command_status(last_command_status){ + LOG(std::string("CommandFailedException, status: ")+ std::to_string(last_command_status), nitrokey::log::Loglevel::DEBUG); + } + + virtual const char *what() const throw() { + return "Command execution has failed on device"; + } + + + bool reason_timestamp_warning() const throw(){ + return last_command_status == static_cast<uint8_t>(cs::timestamp_warning); + } + + bool reason_AES_not_initialized() const throw(){ + return last_command_status == static_cast<uint8_t>(cs::AES_dec_failed); + } + + bool reason_not_authorized() const throw(){ + return last_command_status == static_cast<uint8_t>(cs::not_authorized); + } + + bool reason_slot_not_programmed() const throw(){ + return last_command_status == static_cast<uint8_t>(cs::slot_not_programmed); + } + + bool reason_wrong_password() const throw(){ + return last_command_status == static_cast<uint8_t>(cs::wrong_password); + } + + bool reason_smartcard_busy() const throw(){ + return last_command_status == static_cast<uint8_t>(cs2::smartcard_error); + } + +}; + + +#endif //LIBNITROKEY_COMMANDFAILEDEXCEPTION_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/DeviceCommunicationExceptions.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/DeviceCommunicationExceptions.h new file mode 100644 index 0000000..f710d0b --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/DeviceCommunicationExceptions.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + + +#ifndef LIBNITROKEY_DEVICECOMMUNICATIONEXCEPTIONS_H +#define LIBNITROKEY_DEVICECOMMUNICATIONEXCEPTIONS_H + +#include <atomic> +#include <exception> +#include <stdexcept> +#include <string> + + +class DeviceCommunicationException: public std::runtime_error +{ + std::string message; + static std::atomic_int occurred; +public: + DeviceCommunicationException(std::string _msg): std::runtime_error(_msg), message(_msg){ + ++occurred; + } + uint8_t getType() const {return 1;}; +// virtual const char* what() const throw() override { +// return message.c_str(); +// } + static bool has_occurred(){ return occurred > 0; }; + static void reset_occurred_flag(){ occurred = 0; }; +}; + +class DeviceNotConnected: public DeviceCommunicationException { +public: + DeviceNotConnected(std::string msg) : DeviceCommunicationException(msg){} + uint8_t getType() const {return 2;}; +}; + +class DeviceSendingFailure: public DeviceCommunicationException { +public: + DeviceSendingFailure(std::string msg) : DeviceCommunicationException(msg){} + uint8_t getType() const {return 3;}; +}; + +class DeviceReceivingFailure: public DeviceCommunicationException { +public: + DeviceReceivingFailure(std::string msg) : DeviceCommunicationException(msg){} + uint8_t getType() const {return 4;}; +}; + +class InvalidCRCReceived: public DeviceReceivingFailure { +public: + InvalidCRCReceived(std::string msg) : DeviceReceivingFailure(msg){} + uint8_t getType() const {return 5;}; +}; + + +#endif //LIBNITROKEY_DEVICECOMMUNICATIONEXCEPTIONS_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LibraryException.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LibraryException.h new file mode 100644 index 0000000..3b9d177 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LibraryException.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LIBNITROKEY_LIBRARYEXCEPTION_H +#define LIBNITROKEY_LIBRARYEXCEPTION_H + +#include <exception> +#include <cstdint> +#include <string> +#include "log.h" + +class LibraryException: std::exception { +public: + virtual uint8_t exception_id()= 0; +}; + +class TargetBufferSmallerThanSource: public LibraryException { +public: + virtual uint8_t exception_id() override { + return 203; + } + +public: + size_t source_size; + size_t target_size; + + TargetBufferSmallerThanSource( + size_t source_size, size_t target_size + ) : source_size(source_size), target_size(target_size) {} + + virtual const char *what() const throw() override { + std::string s = " "; + auto ts = [](size_t x){ return std::to_string(x); }; + std::string msg = std::string("Target buffer size is smaller than source: [source size, buffer size]") + +s+ ts(source_size) +s+ ts(target_size); + return msg.c_str(); + } + +}; + +class InvalidHexString : public LibraryException { +public: + virtual uint8_t exception_id() override { + return 202; + } + +public: + uint8_t invalid_char; + + InvalidHexString (uint8_t invalid_char) : invalid_char( invalid_char) {} + + virtual const char *what() const throw() override { + return "Invalid character in hex string"; + } + +}; + +class InvalidSlotException : public LibraryException { +public: + virtual uint8_t exception_id() override { + return 201; + } + +public: + uint8_t slot_selected; + + InvalidSlotException(uint8_t slot_selected) : slot_selected(slot_selected) {} + + virtual const char *what() const throw() override { + return "Wrong slot selected"; + } + +}; + + + +class TooLongStringException : public LibraryException { +public: + virtual uint8_t exception_id() override { + return 200; + } + + std::size_t size_source; + std::size_t size_destination; + std::string message; + + TooLongStringException(size_t size_source, size_t size_destination, const std::string &message = "") : size_source( + size_source), size_destination(size_destination), message(message) { + LOG(std::string("TooLongStringException, size diff: ")+ std::to_string(size_source-size_destination), nitrokey::log::Loglevel::DEBUG); + + } + + virtual const char *what() const throw() override { + //TODO add sizes and message data to final message + return "Too long string has been supplied as an argument"; + } + +}; + +#endif //LIBNITROKEY_LIBRARYEXCEPTION_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LongOperationInProgressException.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LongOperationInProgressException.h new file mode 100644 index 0000000..865d6b5 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/LongOperationInProgressException.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H +#define LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H + +#include "CommandFailedException.h" + +class LongOperationInProgressException : public CommandFailedException { + +public: + unsigned char progress_bar_value; + + LongOperationInProgressException( + unsigned char _command_id, uint8_t last_command_status, unsigned char _progress_bar_value) + : CommandFailedException(_command_id, last_command_status), progress_bar_value(_progress_bar_value){ + LOG( + std::string("LongOperationInProgressException, progress bar status: ")+ + std::to_string(progress_bar_value), nitrokey::log::Loglevel::DEBUG); + } + virtual const char *what() const throw() { + return "Device returned busy status with long operation in progress"; + } +}; + + +#endif //LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/NitrokeyManager.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/NitrokeyManager.h new file mode 100644 index 0000000..d6e5df4 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/NitrokeyManager.h @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LIBNITROKEY_NITROKEYMANAGER_H +#define LIBNITROKEY_NITROKEYMANAGER_H + +#include "device.h" +#include "log.h" +#include "device_proto.h" +#include "stick10_commands.h" +#include "stick10_commands_0.8.h" +#include "stick20_commands.h" +#include <vector> +#include <memory> +#include <unordered_map> + +namespace nitrokey { + using namespace nitrokey::device; + using namespace std; + using namespace nitrokey::proto::stick10; + using namespace nitrokey::proto::stick20; + using namespace nitrokey::proto; + using namespace nitrokey::log; + + +#ifdef __WIN32 +char * strndup(const char* str, size_t maxlen); +#endif + + class NitrokeyManager { + public: + static shared_ptr <NitrokeyManager> instance(); + + bool first_authenticate(const char *pin, const char *temporary_password); + bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + string get_HOTP_code(uint8_t slot_number, const char *user_temporary_password); + string get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, + uint8_t last_interval, + const char *user_temporary_password); + string get_TOTP_code(uint8_t slot_number, const char *user_temporary_password); + stick10::ReadSlot::ResponsePayload get_TOTP_slot_data(const uint8_t slot_number); + stick10::ReadSlot::ResponsePayload get_HOTP_slot_data(const uint8_t slot_number); + + bool set_time(uint64_t time); + /** + * Set the device time used for TOTP to the given time. Contrary to + * {@code set_time(uint64_t)}, this command fails if {@code old_time} + * > {@code time} or if {@code old_time} is zero (where {@code + * old_time} is the current time on the device). + * + * @param time new device time as Unix timestamp (seconds since + * 1970-01-01) + */ + void set_time_soft(uint64_t time); + + [[deprecated("get_time is deprecated -- use set_time_soft instead")]] + 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<std::string> list_devices_by_cpuID(); + + /** + * Connect to the device using unique smartcard:datacard id. + * Needs list_device_by_cpuID() run first + * @param id Current ID of the target device + * @return true on success, false on failure + */ + bool connect_with_ID(const std::string id); + bool connect_with_path (std::string path); + bool connect(const char *device_model); + bool connect(device::DeviceModel device_model); + bool connect(); + bool disconnect(); + bool is_connected() throw() ; + bool could_current_device_be_enumerated(); + bool set_default_commands_delay(int delay); + + DeviceModel get_connected_device_model() const; + void set_debug(bool state); + stick10::GetStatus::ResponsePayload get_status(); + string get_status_as_string(); + string get_serial_number(); + + char * get_totp_slot_name(uint8_t slot_number); + char * get_hotp_slot_name(uint8_t slot_number); + + void change_user_PIN(const char *current_PIN, const char *new_PIN); + void change_admin_PIN(const char *current_PIN, const char *new_PIN); + + void enable_password_safe(const char *user_pin); + + vector <uint8_t> get_password_safe_slot_status(); + + uint8_t get_admin_retry_count(); + uint8_t get_user_retry_count(); + + void lock_device(); + + char * get_password_safe_slot_name(uint8_t slot_number); + char * get_password_safe_slot_password(uint8_t slot_number); + char * get_password_safe_slot_login(uint8_t slot_number); + + void + write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login, + const char *slot_password); + + void erase_password_safe_slot(uint8_t slot_number); + + void user_authenticate(const char *user_password, const char *temporary_password); + + void factory_reset(const char *admin_password); + + void build_aes_key(const char *admin_password); + + void unlock_user_password(const char *admin_password, const char *new_user_password); + + void write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password, + bool delete_user_password, const char *admin_temporary_password); + + vector<uint8_t> read_config(); + + bool is_AES_supported(const char *user_password); + + void unlock_encrypted_volume(const char *user_password); + void lock_encrypted_volume(); + + void unlock_hidden_volume(const char *hidden_volume_password); + void lock_hidden_volume(); + + /** + * Sets unencrypted volume read-only. + * Works until v0.48 (incl. v0.50), where User PIN was sufficient + * Does nothing otherwise. + * @param user_pin User PIN + */ + void set_unencrypted_read_only(const char *user_pin); + + /** + * Sets unencrypted volume read-only. + * Works from v0.49 (except v0.50) accepts Admin PIN + * Does nothing otherwise. + * @param admin_pin Admin PIN + */ + void set_unencrypted_read_only_admin(const char *admin_pin); + + /** + * Sets unencrypted volume read-write. + * Works until v0.48 (incl. v0.50), where User PIN was sufficient + * Does nothing otherwise. + * @param user_pin User PIN + */ + void set_unencrypted_read_write(const char *user_pin); + + /** + * Sets unencrypted volume read-write. + * Works from v0.49 (except v0.50) accepts Admin PIN + * Does nothing otherwise. + * @param admin_pin Admin PIN + */ + void set_unencrypted_read_write_admin(const char *admin_pin); + + void export_firmware(const char *admin_pin); + void enable_firmware_update(const char *firmware_pin); + + void clear_new_sd_card_warning(const char *admin_pin); + + void fill_SD_card_with_random_data(const char *admin_pin); + + uint8_t get_SD_card_size(); + + void change_update_password(const char *current_update_password, const char *new_update_password); + + void create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, + const char *hidden_volume_password); + + void send_startup(uint64_t seconds_from_epoch); + + char * get_status_storage_as_string(); + stick20::DeviceConfigurationResponsePacket::ResponsePayload get_status_storage(); + + char * get_SD_usage_data_as_string(); + std::pair<uint8_t,uint8_t> get_SD_usage_data(); + + + int get_progress_bar_value(); + + ~NitrokeyManager(); + bool is_authorization_command_supported(); + bool is_320_OTP_secret_supported(); + + + 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(); + + explicit NitrokeyManager(); + void set_log_function(std::function<void(std::string)> log_function); + private: + + static shared_ptr <NitrokeyManager> _instance; + std::shared_ptr<Device> device; + std::string current_device_id; + public: + const string get_current_device_id() const; + + private: + std::unordered_map<std::string, shared_ptr<Device> > connected_devices; + std::unordered_map<std::string, shared_ptr<Device> > connected_devices_byID; + + + stick10::ReadSlot::ResponsePayload get_OTP_slot_data(const uint8_t slot_number); + bool is_valid_hotp_slot_number(uint8_t slot_number) const; + bool is_valid_totp_slot_number(uint8_t slot_number) const; + bool is_valid_password_safe_slot_number(uint8_t slot_number) const; + uint8_t get_internal_slot_number_for_hotp(uint8_t slot_number) const; + uint8_t get_internal_slot_number_for_totp(uint8_t slot_number) const; + bool erase_slot(uint8_t slot_number, const char *temporary_password); + char * get_slot_name(uint8_t slot_number); + + template <typename ProCommand, PasswordKind StoKind> + void change_PIN_general(const char *current_PIN, const char *new_PIN); + + void write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + + void write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + + void write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name, const char *secret, + uint64_t counter_or_interval, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) const; + bool _disconnect_no_lock(); + + public: + bool set_current_device_speed(int retry_delay, int send_receive_delay); + void set_loglevel(Loglevel loglevel); + + void set_loglevel(int loglevel); + + /** + * Sets encrypted volume read-only. + * Supported from future versions of Storage. + * @param admin_pin Admin PIN + */ + void set_encrypted_volume_read_only(const char *admin_pin); + + /** + * Sets encrypted volume read-write. + * Supported from future versions of Storage. + * @param admin_pin Admin PIN + */ + void set_encrypted_volume_read_write(const char *admin_pin); + + int get_major_firmware_version(); + + bool is_smartcard_in_use(); + + /** + * Function to determine unencrypted volume PIN type + * @param minor_firmware_version + * @return Returns true, if set unencrypted volume ro/rw pin type is User, false otherwise. + */ + bool set_unencrypted_volume_rorw_pin_type_user(); + + /** + * Blink red and green LED alternatively and infinitely (until device is reconnected). + */ + void wink(); + + stick20::ProductionTest::ResponsePayload production_info(); + }; +} + + + +#endif //LIBNITROKEY_NITROKEYMANAGER_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command.h new file mode 100644 index 0000000..6852bf0 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef COMMAND_H +#define COMMAND_H +#include <string> +#include "command_id.h" +#include "cxx_semantics.h" + +#define print_to_ss(x) ( ss << " " << (#x) <<":\t" << (x) << std::endl ); +#ifdef LOG_VOLATILE_DATA +#define print_to_ss_volatile(x) print_to_ss(x); +#else +#define print_to_ss_volatile(x) ( ss << " " << (#x) <<":\t" << "***********" << std::endl ); +#endif +#define hexdump_to_ss(x) (ss << #x":\n"\ + << ::nitrokey::misc::hexdump((const uint8_t *) (&x), sizeof x, false)); + +namespace nitrokey { + namespace proto { + + template<CommandID cmd_id> + class Command : semantics::non_constructible { + public: + constexpr static CommandID command_id() { return cmd_id; } + + template<typename T> + std::string dissect(const T &) { + return std::string("Payload dissection is unavailable"); + } + }; + +namespace stick20{ + enum class PasswordKind : uint8_t { + User = 'P', + Admin = 'A', + AdminPrefixed + }; + + template<CommandID cmd_id, PasswordKind Tpassword_kind = PasswordKind::User, int password_length = 20> + class PasswordCommand : public Command<cmd_id> { + constexpr static CommandID _command_id() { return cmd_id; } + public: + struct CommandPayload { + uint8_t kind; + uint8_t password[password_length]; + + std::string dissect() const { + std::stringstream ss; + print_to_ss( kind ); + print_to_ss_volatile(password); + return ss.str(); + } + void set_kind_admin() { + kind = (uint8_t) 'A'; + } + void set_kind_admin_prefixed() { + kind = (uint8_t) 'P'; + } + void set_kind_user() { + kind = (uint8_t) 'P'; + } + + void set_defaults(){ + set_kind(Tpassword_kind); + } + + void set_kind(PasswordKind password_kind){ + switch (password_kind){ + case PasswordKind::Admin: + set_kind_admin(); + break; + case PasswordKind::User: + set_kind_user(); + break; + case PasswordKind::AdminPrefixed: + set_kind_admin_prefixed(); + break; + } + }; + + } __packed; + + //typedef Transaction<Command<cmd_id>::command_id(), struct CommandPayload, struct EmptyPayload> + // CommandTransaction; + using CommandTransaction = Transaction<cmd_id, CommandPayload, EmptyPayload>; + //using CommandTransaction = Transaction<_command_id(), CommandPayload, EmptyPayload>; + + }; + } + } +} + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command_id.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command_id.h new file mode 100644 index 0000000..eb0d450 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/command_id.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef COMMAND_ID_H +#define COMMAND_ID_H +#include <stdint.h> + +namespace nitrokey { +namespace proto { + namespace stick20 { + enum class device_status : uint8_t { + idle = 0, + ok, + busy, + wrong_password, + busy_progressbar, + password_matrix_ready, + no_user_password_unlock, // FIXME: translate on receive to command status error (fix in firmware?) + smartcard_error, + security_bit_active + }; + const int CMD_START_VALUE = 0x20; + const int CMD_END_VALUE = 0x60; + } + namespace stick10 { + enum class command_status : uint8_t { + ok = 0, + wrong_CRC, + wrong_slot, + slot_not_programmed, + wrong_password = 4, + not_authorized, + timestamp_warning, + no_name_error, + not_supported, + unknown_command, + AES_dec_failed + }; + enum class device_status : uint8_t { + ok = 0, + busy = 1, + error, + received_report, + }; + } + + +enum class CommandID : uint8_t { + GET_STATUS = 0x00, + WRITE_TO_SLOT = 0x01, + READ_SLOT_NAME = 0x02, + READ_SLOT = 0x03, + GET_CODE = 0x04, + WRITE_CONFIG = 0x05, + ERASE_SLOT = 0x06, + FIRST_AUTHENTICATE = 0x07, + AUTHORIZE = 0x08, + GET_PASSWORD_RETRY_COUNT = 0x09, + CLEAR_WARNING = 0x0A, + SET_TIME = 0x0B, + TEST_COUNTER = 0x0C, + TEST_TIME = 0x0D, + USER_AUTHENTICATE = 0x0E, + GET_USER_PASSWORD_RETRY_COUNT = 0x0F, + USER_AUTHORIZE = 0x10, + UNLOCK_USER_PASSWORD = 0x11, + LOCK_DEVICE = 0x12, + FACTORY_RESET = 0x13, + CHANGE_USER_PIN = 0x14, + CHANGE_ADMIN_PIN = 0x15, + WRITE_TO_SLOT_2 = 0x16, + SEND_OTP_DATA = 0x17, + + ENABLE_CRYPTED_PARI = 0x20, + DISABLE_CRYPTED_PARI = 0x20 + 1, + ENABLE_HIDDEN_CRYPTED_PARI = 0x20 + 2, + DISABLE_HIDDEN_CRYPTED_PARI = 0x20 + 3, + ENABLE_FIRMWARE_UPDATE = 0x20 + 4, //enables update mode + EXPORT_FIRMWARE_TO_FILE = 0x20 + 5, + GENERATE_NEW_KEYS = 0x20 + 6, + FILL_SD_CARD_WITH_RANDOM_CHARS = 0x20 + 7, + + WRITE_STATUS_DATA = 0x20 + 8, //@unused + ENABLE_READONLY_UNCRYPTED_LUN = 0x20 + 9, + ENABLE_READWRITE_UNCRYPTED_LUN = 0x20 + 10, + + SEND_PASSWORD_MATRIX = 0x20 + 11, //@unused + SEND_PASSWORD_MATRIX_PINDATA = 0x20 + 12, //@unused + SEND_PASSWORD_MATRIX_SETUP = 0x20 + 13, //@unused + + GET_DEVICE_STATUS = 0x20 + 14, + SEND_DEVICE_STATUS = 0x20 + 15, + + SEND_HIDDEN_VOLUME_PASSWORD = 0x20 + 16, //@unused + SEND_HIDDEN_VOLUME_SETUP = 0x20 + 17, + SEND_PASSWORD = 0x20 + 18, + SEND_NEW_PASSWORD = 0x20 + 19, + CLEAR_NEW_SD_CARD_FOUND = 0x20 + 20, + + SEND_STARTUP = 0x20 + 21, + SEND_CLEAR_STICK_KEYS_NOT_INITIATED = 0x20 + 22, + SEND_LOCK_STICK_HARDWARE = 0x20 + 23, //locks firmware upgrade + + PRODUCTION_TEST = 0x20 + 24, + SEND_DEBUG_DATA = 0x20 + 25, //@unused + + CHANGE_UPDATE_PIN = 0x20 + 26, + + //added in v0.48.5 + ENABLE_ADMIN_READONLY_UNCRYPTED_LUN = 0x20 + 28, + ENABLE_ADMIN_READWRITE_UNCRYPTED_LUN = 0x20 + 29, + ENABLE_ADMIN_READONLY_ENCRYPTED_LUN = 0x20 + 30, + ENABLE_ADMIN_READWRITE_ENCRYPTED_LUN = 0x20 + 31, + CHECK_SMARTCARD_USAGE = 0x20 + 32, + //v0.52+ + WINK = 0x20 + 33, + + GET_PW_SAFE_SLOT_STATUS = 0x60, + GET_PW_SAFE_SLOT_NAME = 0x61, + GET_PW_SAFE_SLOT_PASSWORD = 0x62, + GET_PW_SAFE_SLOT_LOGINNAME = 0x63, + SET_PW_SAFE_SLOT_DATA_1 = 0x64, + SET_PW_SAFE_SLOT_DATA_2 = 0x65, + PW_SAFE_ERASE_SLOT = 0x66, + PW_SAFE_ENABLE = 0x67, + PW_SAFE_INIT_KEY = 0x68, //@unused + PW_SAFE_SEND_DATA = 0x69, //@unused + SD_CARD_HIGH_WATERMARK = 0x70, + DETECT_SC_AES = 0x6a, + NEW_AES_KEY = 0x6b +}; + +const char *commandid_to_string(CommandID id); +} +} +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/cxx_semantics.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/cxx_semantics.h new file mode 100644 index 0000000..36ed142 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/cxx_semantics.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef CXX_SEMANTICS_H +#define CXX_SEMANTICS_H + +#ifndef _MSC_VER +#define __packed __attribute__((__packed__)) +#else +#define __packed +#endif + +#ifdef _MSC_VER +#define strdup _strdup +#endif + +/* + * There's no need to include Boost for a simple subset this project needs. + */ +namespace semantics { +class non_constructible { + non_constructible() {} +}; +} + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/deprecated.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/deprecated.h new file mode 100644 index 0000000..5a83288 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/deprecated.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + + +#ifndef LIBNITROKEY_DEPRECATED_H +#define LIBNITROKEY_DEPRECATED_H + +#if defined(__GNUC__) || defined(__clang__) +#define DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED __declspec(deprecated) +#else +#pragma message("WARNING: DEPRECATED macro is not defined for this compiler") +#define DEPRECATED +#endif + +#endif //LIBNITROKEY_DEPRECATED_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device.h new file mode 100644 index 0000000..f6d2380 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef DEVICE_H +#define DEVICE_H +#include <chrono> +#include "hidapi/hidapi.h" +#include <cstdint> +#include <string> +#include <vector> + +#define HID_REPORT_SIZE 65 + +#include <atomic> + +namespace nitrokey { +namespace device { + using namespace std::chrono_literals; + using std::chrono::milliseconds; + + struct EnumClassHash + { + template <typename T> + std::size_t operator()(T t) const + { + return static_cast<std::size_t>(t); + } + }; + +enum class DeviceModel{ + PRO, + STORAGE +}; + +#include <atomic> + +class Device { + +public: + + struct ErrorCounters{ + using cnt = std::atomic_int; + cnt wrong_CRC; + cnt CRC_other_than_awaited; + cnt busy; + cnt total_retries; + cnt sending_error; + cnt receiving_error; + cnt total_comm_runs; + cnt successful_storage_commands; + cnt command_successful_recv; + cnt recv_executed; + cnt sends_executed; + cnt busy_progressbar; + cnt command_result_not_equal_0_recv; + cnt communication_successful; + cnt low_level_reconnect; + std::string get_as_string(); + + } m_counters = {}; + + + 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); + + virtual ~Device(); + + // lack of device is not actually an error, + // so it doesn't throw + virtual bool connect(); + virtual bool disconnect(); + + /* + * Sends packet of HID_REPORT_SIZE. + */ + virtual int send(const void *packet); + + /* + * Gets packet of HID_REPORT_SIZE. + * Can sleep. See below. + */ + virtual int recv(void *packet); + + /*** + * Returns true if some device is visible by OS with given VID and PID + * whether the device is connected through HID API or not. + * @return true if visible by OS + */ + bool could_be_enumerated(); + std::vector<std::string> enumerate(); + + + void show_stats(); +// ErrorCounters get_stats(){ return m_counters; } + int get_retry_receiving_count() const { return m_retry_receiving_count; }; + int get_retry_sending_count() const { return m_retry_sending_count; }; + std::chrono::milliseconds get_retry_timeout() const { return m_retry_timeout; }; + std::chrono::milliseconds get_send_receive_delay() const {return m_send_receive_delay;} + + int get_last_command_status() {int a = std::atomic_exchange(&last_command_status, static_cast<uint8_t>(0)); return a;}; + void set_last_command_status(uint8_t _err) { last_command_status = _err;} ; + bool last_command_sucessfull() const {return last_command_status == 0;}; + DeviceModel get_device_model() const {return m_model;} + void set_receiving_delay(std::chrono::milliseconds delay); + void set_retry_delay(std::chrono::milliseconds delay); + static void set_default_device_speed(int delay); + void setDefaultDelay(); + void set_path(const std::string path); + + + private: + std::atomic<uint8_t> last_command_status; + void _reconnect(); + bool _connect(); + bool _disconnect(); + +protected: + const uint16_t m_vid; + const uint16_t m_pid; + const DeviceModel m_model; + + /* + * While the project uses Signal11 portable HIDAPI + * library, there's no way of doing it asynchronously, + * hence polling. + */ + const int m_retry_sending_count; + const int m_retry_receiving_count; + std::chrono::milliseconds m_retry_timeout; + std::chrono::milliseconds m_send_receive_delay; + std::atomic<hid_device *>mp_devhandle; + std::string m_path; + + static std::atomic_int instances_count; + static std::chrono::milliseconds default_delay ; +}; + +class Stick10 : public Device { + public: + Stick10(); + +}; + +class Stick20 : public Device { + public: + Stick20(); +}; +} +} +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device_proto.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device_proto.h new file mode 100644 index 0000000..45a6c16 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/device_proto.h @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef DEVICE_PROTO_H +#define DEVICE_PROTO_H + +#include <utility> +#include <thread> +#include <type_traits> +#include <stdexcept> +#include <string> +// a local version for compatibility with Windows +#include <stdint.h> +#include "cxx_semantics.h" +#include "device.h" +#include "misc.h" +#include "log.h" +#include "command_id.h" +#include "dissect.h" +#include "CommandFailedException.h" +#include "LongOperationInProgressException.h" + +#define STICK20_UPDATE_MODE_VID 0x03EB +#define STICK20_UPDATE_MODE_PID 0x2FF1 + +#define PAYLOAD_SIZE 53 +#define PWS_SLOT_COUNT 16 +#define PWS_SLOTNAME_LENGTH 11 +#define PWS_PASSWORD_LENGTH 20 +#define PWS_LOGINNAME_LENGTH 32 + +#define PWS_SEND_PASSWORD 0 +#define PWS_SEND_LOGINNAME 1 +#define PWS_SEND_TAB 2 +#define PWS_SEND_CR 3 + +#include <mutex> +#include "DeviceCommunicationExceptions.h" +#define bzero(b,len) (memset((b), '\0', (len)), (void) 0) + +namespace nitrokey { + namespace proto { + extern std::mutex send_receive_mtx; + + +/* + * POD types for HID proto commands + * Instances are meant to be __packed. + * + * TODO (future) support for Big Endian + */ +#pragma pack (push,1) +/* + * Every packet is a USB HID report (check USB spec) + */ + template<CommandID cmd_id, typename Payload> + struct HIDReport { + uint8_t _zero; + CommandID command_id; // uint8_t + union { + uint8_t _padding[HID_REPORT_SIZE - 6]; + Payload payload; + } __packed; + uint32_t crc; + + // POD types can't have non-default constructors + // used in Transaction<>::run() + void initialize() { + bzero(this, sizeof *this); + command_id = cmd_id; + } + + uint32_t calculate_CRC() const { + // w/o leading zero, a part of each HID packet + // w/o 4-byte crc + return misc::stm_crc32((const uint8_t *) (this) + 1, + (size_t) (HID_REPORT_SIZE - 5)); + } + + void update_CRC() { crc = calculate_CRC(); } + + bool isCRCcorrect() const { return crc == calculate_CRC(); } + + bool isValid() const { + return true; + // return !_zero && payload.isValid() && isCRCcorrect(); + } + + operator std::string() const { + // Packet type is known upfront in normal operation. + // Can't be used to dissect random packets. + return QueryDissector<cmd_id, decltype(*this)>::dissect(*this); + } + } __packed; + +/* + * Response payload (the parametrized type inside struct HIDReport) + * + * command_id member in incoming HIDReport structure carries the command + * type last used. + */ + namespace DeviceResponseConstants{ + //magic numbers from firmware + static constexpr auto storage_status_absolute_address = 21; + static constexpr auto storage_data_absolute_address = storage_status_absolute_address + 5; + static constexpr auto header_size = 8; //from _zero to last_command_status inclusive + static constexpr auto footer_size = 4; //crc + static constexpr auto wrapping_size = header_size + footer_size; + } + + template<CommandID cmd_id, typename ResponsePayload> + struct DeviceResponse { + static constexpr auto storage_status_padding_size = + DeviceResponseConstants::storage_status_absolute_address - DeviceResponseConstants::header_size; + + uint8_t _zero; + uint8_t device_status; + uint8_t command_id; // originally last_command_type + uint32_t last_command_crc; + uint8_t last_command_status; + + union { + uint8_t _padding[HID_REPORT_SIZE - DeviceResponseConstants::wrapping_size]; + ResponsePayload payload; + struct { + uint8_t _storage_status_padding[storage_status_padding_size]; + uint8_t command_counter; + uint8_t command_id; + uint8_t device_status; //@see stick20::device_status + uint8_t progress_bar_value; + } __packed storage_status; + } __packed; + + uint32_t crc; + + void initialize() { bzero(this, sizeof *this); } + + uint32_t calculate_CRC() const { + // w/o leading zero, a part of each HID packet + // w/o 4-byte crc + return misc::stm_crc32((const uint8_t *) (this) + 1, + (size_t) (HID_REPORT_SIZE - 5)); + } + + void update_CRC() { crc = calculate_CRC(); } + bool isCRCcorrect() const { return crc == calculate_CRC(); } + bool isValid() const { + // return !_zero && payload.isValid() && isCRCcorrect() && + // command_id == (uint8_t)(cmd_id); + return crc != 0; + } + + operator std::string() const { + return ResponseDissector<cmd_id, decltype(*this)>::dissect(*this); + } + } __packed; + + struct EmptyPayload { + bool isValid() const { return true; } + + std::string dissect() const { return std::string("Empty Payload."); } + } __packed; + + template<typename command_packet, typename response_payload> + class ClearingProxy { + public: + ClearingProxy(command_packet &p) { + packet = p; + bzero(&p, sizeof(p)); + } + + ~ClearingProxy() { + bzero(&packet, sizeof(packet)); + } + + response_payload &data() { + return packet.payload; + } + + command_packet packet; + }; + + template<CommandID cmd_id, typename command_payload, typename response_payload> + class Transaction : semantics::non_constructible { + public: + // Types declared in command class scope can't be reached from there. + typedef command_payload CommandPayload; + typedef response_payload ResponsePayload; + + + typedef struct HIDReport<cmd_id, CommandPayload> OutgoingPacket; + typedef struct DeviceResponse<cmd_id, ResponsePayload> ResponsePacket; +#pragma pack (pop) + + static_assert(std::is_pod<OutgoingPacket>::value, + "outgoingpacket must be a pod type"); + static_assert(std::is_pod<ResponsePacket>::value, + "ResponsePacket must be a POD type"); + static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE, + "OutgoingPacket type is not the right size"); + static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE, + "ResponsePacket type is not the right size"); + + static uint32_t getCRC( + const command_payload &payload) { + OutgoingPacket outp; + outp.initialize(); + outp.payload = payload; + outp.update_CRC(); + return outp.crc; + } + + template<typename T> + static void clear_packet(T &st) { + bzero(&st, sizeof(st)); + } + + static ClearingProxy<ResponsePacket, response_payload> run(std::shared_ptr<device::Device> dev, + const command_payload &payload) { + using namespace ::nitrokey::device; + using namespace ::nitrokey::log; + using namespace std::chrono_literals; + + std::lock_guard<std::mutex> guard(send_receive_mtx); + + LOG(__FUNCTION__, Loglevel::DEBUG_L2); + + if (dev == nullptr){ + LOG(std::string("Throw: Device not initialized"), Loglevel::DEBUG_L1); + throw DeviceNotConnected("Device not initialized"); + } + dev->m_counters.total_comm_runs++; + + int status; + OutgoingPacket outp; + ResponsePacket resp; + + // POD types can't have non-default constructors + outp.initialize(); + resp.initialize(); + + outp.payload = payload; + outp.update_CRC(); + + LOG("-------------------", Loglevel::DEBUG); + LOG("Outgoing HID packet:", Loglevel::DEBUG); + LOG(static_cast<std::string>(outp), Loglevel::DEBUG); + LOG(std::string("=> ") + std::string(commandid_to_string(static_cast<CommandID>(outp.command_id))), Loglevel::DEBUG_L1); + + + if (!outp.isValid()) { + LOG(std::string("Throw: Invalid outgoing packet"), Loglevel::DEBUG_L1); + throw DeviceSendingFailure("Invalid outgoing packet"); + } + + bool successful_communication = false; + int receiving_retry_counter = 0; + int sending_retry_counter = dev->get_retry_sending_count(); + while (sending_retry_counter-- > 0) { + dev->m_counters.sends_executed++; + status = dev->send(&outp); + if (status <= 0){ + //FIXME early disconnection not yet working properly +// LOG("Encountered communication error, disconnecting device", Loglevel::DEBUG_L2); +// dev->disconnect(); + dev->m_counters.sending_error++; + LOG(std::string("Throw: Device error while sending command "), Loglevel::DEBUG_L1); + throw DeviceSendingFailure( + std::string("Device error while sending command ") + + std::to_string(status)); + } + + std::this_thread::sleep_for(dev->get_send_receive_delay()); + + // FIXME make checks done in device:recv here + receiving_retry_counter = dev->get_retry_receiving_count(); + int busy_counter = 0; + auto retry_timeout = dev->get_retry_timeout(); + while (receiving_retry_counter-- > 0) { + dev->m_counters.recv_executed++; + status = dev->recv(&resp); + + if (dev->get_device_model() == DeviceModel::STORAGE && + resp.command_id >= stick20::CMD_START_VALUE && + resp.command_id < stick20::CMD_END_VALUE ) { + LOG(std::string("Detected storage device cmd, status: ") + + std::to_string(resp.storage_status.device_status), Loglevel::DEBUG_L2); + + resp.last_command_status = static_cast<uint8_t>(stick10::command_status::ok); + switch (static_cast<stick20::device_status>(resp.storage_status.device_status)) { + case stick20::device_status::idle : + case stick20::device_status::ok: + resp.device_status = static_cast<uint8_t>(stick10::device_status::ok); + break; + case stick20::device_status::busy: + case stick20::device_status::busy_progressbar: //TODO this will be modified later for getting progressbar status + resp.device_status = static_cast<uint8_t>(stick10::device_status::busy); + break; + case stick20::device_status::wrong_password: + resp.last_command_status = static_cast<uint8_t>(stick10::command_status::wrong_password); + resp.device_status = static_cast<uint8_t>(stick10::device_status::ok); + break; + case stick20::device_status::no_user_password_unlock: + resp.last_command_status = static_cast<uint8_t>(stick10::command_status::AES_dec_failed); + resp.device_status = static_cast<uint8_t>(stick10::device_status::ok); + break; + default: + LOG(std::string("Unknown storage device status, cannot translate: ") + + std::to_string(resp.storage_status.device_status), Loglevel::DEBUG); + resp.device_status = resp.storage_status.device_status; + break; + }; + } + + //Some of the commands return wrong CRC, for now skip checking it (TODO list and report) + //if (resp.device_status == 0 && resp.last_command_crc == outp.crc && resp.isCRCcorrect()) break; + auto CRC_equal_awaited = true; // resp.last_command_crc == outp.crc; + if (resp.device_status == static_cast<uint8_t>(stick10::device_status::ok) && + CRC_equal_awaited && resp.isValid()){ + successful_communication = true; + break; + } + if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy)) { + dev->m_counters.busy++; + + if (busy_counter++<10) { + receiving_retry_counter++; + LOG("Status busy, not decreasing receiving_retry_counter counter: " + + std::to_string(receiving_retry_counter), Loglevel::DEBUG_L2); + } else { + retry_timeout *= 2; + retry_timeout = std::min(retry_timeout, 300ms); + busy_counter = 0; + LOG("Status busy, decreasing receiving_retry_counter counter: " + + std::to_string(receiving_retry_counter) + ", current delay:" + + std::to_string(retry_timeout.count()), Loglevel::DEBUG); + LOG(std::string("Busy retry: status ") + + std::to_string(resp.storage_status.device_status) + + ", " + + std::to_string(retry_timeout.count()) + + "ms, counter " + + std::to_string(receiving_retry_counter) + + ", progress: " + + std::to_string(resp.storage_status.progress_bar_value) + , Loglevel::DEBUG_L1); + } + } + if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy) && + static_cast<stick20::device_status>(resp.storage_status.device_status) + == stick20::device_status::busy_progressbar){ + successful_communication = true; + break; + } + LOG(std::string("Retry status - dev status, awaited cmd crc, correct packet CRC: ") + + std::to_string(resp.device_status) + + " " + std::to_string(CRC_equal_awaited) + + " " + std::to_string(resp.isCRCcorrect()), Loglevel::DEBUG_L2); + + if (!resp.isCRCcorrect()) dev->m_counters.wrong_CRC++; + if (!CRC_equal_awaited) dev->m_counters.CRC_other_than_awaited++; + + + LOG( + "Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...", + Loglevel::DEBUG_L2); + LOG("Invalid incoming HID packet:", Loglevel::DEBUG_L2); + LOG(static_cast<std::string>(resp), Loglevel::DEBUG_L2); + dev->m_counters.total_retries++; + LOG(".", Loglevel::DEBUG_L1); + std::this_thread::sleep_for(retry_timeout); + continue; + } + if (successful_communication) break; + LOG(std::string("Resending (outer loop) "), Loglevel::DEBUG_L2); + LOG(std::string("sending_retry_counter count: ") + std::to_string(sending_retry_counter), + Loglevel::DEBUG); + } + + if(resp.last_command_crc != outp.crc){ + LOG(std::string("Accepting response with CRC other than expected ") + + "Command ID: " + std::to_string(resp.command_id) + " " + + commandid_to_string(static_cast<CommandID>(resp.command_id)) + " " + + "Reported by response and expected: " + std::to_string(resp.last_command_crc) + "!=" + std::to_string(outp.crc), + Loglevel::WARNING + ); + } + + dev->set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv + + clear_packet(outp); + + + if (status <= 0) { + dev->m_counters.receiving_error++; + LOG(std::string("Throw: Device error while executing command "), Loglevel::DEBUG_L1); + throw DeviceReceivingFailure( //FIXME replace with CriticalErrorException + std::string("Device error while executing command ") + + std::to_string(status)); + } + + LOG(std::string("<= ") + + std::string( + commandid_to_string(static_cast<CommandID>(resp.command_id)) + + std::string(" ") + + std::to_string(resp.device_status) + + std::string(" ") + + std::to_string(resp.storage_status.device_status) +// + std::to_string( status_translate_command(resp.storage_status.device_status)) + ), Loglevel::DEBUG_L1); + + LOG("Incoming HID packet:", Loglevel::DEBUG); + LOG(static_cast<std::string>(resp), Loglevel::DEBUG); + if (dev->get_retry_receiving_count() - receiving_retry_counter > 2) { + LOG(std::string("Packet received with receiving_retry_counter count: ") + + std::to_string(receiving_retry_counter), + Loglevel::DEBUG_L1); + } + + if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy) && + static_cast<stick20::device_status>(resp.storage_status.device_status) + == stick20::device_status::busy_progressbar){ + dev->m_counters.busy_progressbar++; + LOG(std::string("Throw: Long operation in progress exception"), Loglevel::DEBUG_L1); + throw LongOperationInProgressException( + resp.command_id, resp.device_status, resp.storage_status.progress_bar_value); + } + + if (!resp.isValid()) { + LOG(std::string("Throw: Invalid incoming packet"), Loglevel::DEBUG_L1); + throw InvalidCRCReceived("Invalid incoming packet"); + } + if (receiving_retry_counter <= 0){ + LOG(std::string("Throw: \"Maximum receiving_retry_counter count reached for receiving response from the device!\"" + + std::to_string(receiving_retry_counter)), Loglevel::DEBUG_L1); + throw DeviceReceivingFailure( + "Maximum receiving_retry_counter count reached for receiving response from the device!"); + } + dev->m_counters.communication_successful++; + + if (resp.last_command_status != static_cast<uint8_t>(stick10::command_status::ok)){ + dev->m_counters.command_result_not_equal_0_recv++; + LOG(std::string("Throw: CommandFailedException ") + std::to_string(resp.last_command_status), Loglevel::DEBUG_L1); + throw CommandFailedException(resp.command_id, resp.last_command_status); + } + + dev->m_counters.command_successful_recv++; + + if (dev->get_device_model() == DeviceModel::STORAGE && + resp.command_id >= stick20::CMD_START_VALUE && + resp.command_id < stick20::CMD_END_VALUE ) { + dev->m_counters.successful_storage_commands++; + } + + if (!resp.isCRCcorrect()) + LOG(std::string("Accepting response from device with invalid CRC. ") + + "Command ID: " + std::to_string(resp.command_id) + " " + + commandid_to_string(static_cast<CommandID>(resp.command_id)) + " " + + "Reported and calculated: " + std::to_string(resp.crc) + "!=" + std::to_string(resp.calculate_CRC()), + Loglevel::WARNING + ); + + // See: DeviceResponse + return resp; + } + + static ClearingProxy<ResponsePacket, response_payload> run(std::shared_ptr<device::Device> dev) { + command_payload empty_payload; + return run(dev, empty_payload); + } + }; + } +} +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/dissect.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/dissect.h new file mode 100644 index 0000000..690b5b7 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/dissect.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +/* + * Protocol packet dissection + */ +#ifndef DISSECT_H +#define DISSECT_H +#include <string> +#include <sstream> +#include <iomanip> +#include "misc.h" +#include "cxx_semantics.h" +#include "command_id.h" +#include "device_proto.h" + +namespace nitrokey { +namespace proto { + +template <CommandID id, class HIDPacket> +class QueryDissector : semantics::non_constructible { + public: + static std::string dissect(const HIDPacket &pod) { + std::stringstream out; + +#ifdef LOG_VOLATILE_DATA + out << "Raw HID packet:" << std::endl; + out << ::nitrokey::misc::hexdump((const uint8_t *)(&pod), sizeof pod); +#endif + + out << "Contents:" << std::endl; + out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) + << std::endl; + out << "CRC:\t" + << std::hex << std::setw(2) << std::setfill('0') + << pod.crc << std::endl; + + out << "Payload:" << std::endl; + out << pod.payload.dissect(); + return out.str(); + } +}; + + + + +template <CommandID id, class HIDPacket> +class ResponseDissector : semantics::non_constructible { + public: + static std::string status_translate_device(int status){ + auto enum_status = static_cast<proto::stick10::device_status>(status); + switch (enum_status){ + case stick10::device_status::ok: return "OK"; + case stick10::device_status::busy: return "BUSY"; + case stick10::device_status::error: return "ERROR"; + case stick10::device_status::received_report: return "RECEIVED_REPORT"; + } + return std::string("UNKNOWN: ") + std::to_string(status); + } + + static std::string to_upper(std::string str){ + for (auto & c: str) c = toupper(c); + return str; + } + static std::string status_translate_command(int status){ + auto enum_status = static_cast<proto::stick10::command_status >(status); + switch (enum_status) { +#define p(X) case X: return to_upper(std::string(#X)); + p(stick10::command_status::ok) + p(stick10::command_status::wrong_CRC) + p(stick10::command_status::wrong_slot) + p(stick10::command_status::slot_not_programmed) + p(stick10::command_status::wrong_password) + p(stick10::command_status::not_authorized) + p(stick10::command_status::timestamp_warning) + p(stick10::command_status::no_name_error) + p(stick10::command_status::not_supported) + p(stick10::command_status::unknown_command) + p(stick10::command_status::AES_dec_failed) +#undef p + } + return std::string("UNKNOWN: ") + std::to_string(status); + } + + static std::string dissect(const HIDPacket &pod) { + std::stringstream out; + + // FIXME use values from firmware (possibly generate separate + // header automatically) + +#ifdef LOG_VOLATILE_DATA + out << "Raw HID packet:" << std::endl; + out << ::nitrokey::misc::hexdump((const uint8_t *)(&pod), sizeof pod); +#endif + + out << "Device status:\t" << pod.device_status + 0 << " " + << status_translate_device(pod.device_status) << std::endl; + out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) << " hex: " << std::hex << (int)pod.command_id + << std::endl; + out << "Last command CRC:\t" + << std::hex << std::setw(2) << std::setfill('0') + << pod.last_command_crc << std::endl; + out << "Last command status:\t" << pod.last_command_status + 0 << " " + << status_translate_command(pod.last_command_status) << std::endl; + out << "CRC:\t" + << std::hex << std::setw(2) << std::setfill('0') + << pod.crc << std::endl; + if((int)pod.command_id == pod.storage_status.command_id){ + out << "Storage stick status (where applicable):" << std::endl; +#define d(x) out << " "#x": \t"<< std::hex << std::setw(2) \ + << std::setfill('0')<< static_cast<int>(x) << std::endl; + d(pod.storage_status.command_counter); + d(pod.storage_status.command_id); + d(pod.storage_status.device_status); + d(pod.storage_status.progress_bar_value); +#undef d + } + + out << "Payload:" << std::endl; + out << pod.payload.dissect(); + return out.str(); + } +}; +} +} + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/hidapi/hidapi.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/hidapi/hidapi.h new file mode 100644 index 0000000..e5bc2dc --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/hidapi/hidapi.h @@ -0,0 +1,391 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include <wchar.h> + +#ifdef _WIN32 + #define HID_API_EXPORT __declspec(dllexport) + #define HID_API_CALL +#else + #define HID_API_EXPORT /**< API export macro */ + #define HID_API_CALL /**< API call macro */ +#endif + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +#ifdef __cplusplus +extern "C" { +#endif + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac only). */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac only).*/ + unsigned short usage; + /** The USB interface which this logical device + represents. Valid on both Linux implementations + in all cases, and valid on the Windows implementation + only if the device contains more than one interface. */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + + @returns + This function returns a pointer to a linked list of type + struct #hid_device, containing information about the HID devices + attached to the system, or NULL in the case of failure. Free + this linked list by calling hid_free_enumeration(). + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + + This function frees a linked list created by hid_enumerate(). + + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @param path The path name of the device to open + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** @brief Write an Output report to a HID device. + + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + + hid_write() will send the data on the first OUT endpoint, if + one exists. If it does not, it will send the data through + the Control Endpoint (Endpoint 0). + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read within + the timeout period, this function returns 0. + */ + int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read and + the handle is in non-blocking mode, this function returns 0. + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @param device A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + + /** @brief Close a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get a string from a HID device, based on its string index. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a string describing the last error which occurred. + + @ingroup API + @param device A device handle returned from hid_open(). + + @returns + This function returns a string containing the last error + which occurred or NULL if none has occurred. + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/log.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/log.h new file mode 100644 index 0000000..2a64bef --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/log.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LOG_H +#define LOG_H + +#include <string> +#include <functional> + +namespace nitrokey { + namespace log { + +//for MSVC +#ifdef ERROR +#undef ERROR +#endif + + + enum class Loglevel : int { + ERROR, + WARNING, + INFO, + DEBUG_L1, + DEBUG, + DEBUG_L2 + }; + + class LogHandler { + public: + virtual void print(const std::string &, Loglevel lvl) = 0; + protected: + std::string loglevel_to_str(Loglevel); + std::string format_message_to_string(const std::string &str, const Loglevel &lvl); + + }; + + class StdlogHandler : public LogHandler { + public: + virtual void print(const std::string &, Loglevel lvl); + }; + + class FunctionalLogHandler : public LogHandler { + using log_function_type = std::function<void(std::string)>; + log_function_type log_function; + public: + FunctionalLogHandler(log_function_type _log_function); + virtual void print(const std::string &, Loglevel lvl); + + }; + + extern StdlogHandler stdlog_handler; + + class Log { + public: + Log() : mp_loghandler(&stdlog_handler), m_loglevel(Loglevel::WARNING) {} + + static Log &instance() { + if (mp_instance == nullptr) mp_instance = new Log; + return *mp_instance; + } + + void operator()(const std::string &, Loglevel); + void set_loglevel(Loglevel lvl) { m_loglevel = lvl; } + void set_handler(LogHandler *handler) { mp_loghandler = handler; } + + private: + LogHandler *mp_loghandler; + Loglevel m_loglevel; + static std::string prefix; + public: + static void setPrefix(std::string prefix = std::string()); + + private: + + static Log *mp_instance; + }; + } +} + + +#ifdef NO_LOG +#define LOG(string, level) while(false){} +#define LOGD(string) while(false){} +#else +#define LOG(string, level) nitrokey::log::Log::instance()((string), (level)) +#define LOGD1(string) nitrokey::log::Log::instance()((string), (nitrokey::log::Loglevel::DEBUG_L1)) +#define LOGD(string) nitrokey::log::Log::instance()((string), (nitrokey::log::Loglevel::DEBUG_L2)) +#endif + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/misc.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/misc.h new file mode 100644 index 0000000..88254dd --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/misc.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + + +#ifndef MISC_H +#define MISC_H +#include <stdio.h> +#include <string> +#include <vector> +#include <string.h> +#include "log.h" +#include "LibraryException.h" +#include <sstream> +#include <iomanip> + + +namespace nitrokey { +namespace misc { + + template<typename T> + std::string toHex(T value){ + using namespace std; + std::ostringstream oss; + oss << std::hex << std::setw(sizeof(value)*2) << std::setfill('0') << value; + return oss.str(); + } + + /** + * 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. + * @tparam T type of destination array + * @param dest fixed size destination array + * @param src pointer to source c-style valid string + */ + template <typename T> + void strcpyT(T& dest, const char* src){ + + if (src == nullptr) +// 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); + } + strncpy((char*) &dest, src, s_dest); + } + +#define bzero(b,len) (memset((b), '\0', (len)), (void) 0) + template <typename T> +typename T::CommandPayload get_payload(){ + //Create, initialize and return by value command payload + typename T::CommandPayload st; + bzero(&st, sizeof(st)); + return st; +} + + template<typename CMDTYPE, typename Tdev> + void execute_password_command(Tdev &stick, const char *password) { + auto p = get_payload<CMDTYPE>(); + p.set_defaults(); + strcpyT(p.password, password); + CMDTYPE::CommandTransaction::run(stick, p); + } + + std::string hexdump(const uint8_t *p, size_t size, bool print_header=true, bool print_ascii=true, + bool print_empty=true); + uint32_t stm_crc32(const uint8_t *data, size_t size); + std::vector<uint8_t> hex_string_to_byte(const char* hexString); +} +} + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands.h new file mode 100644 index 0000000..f2ffba2 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands.h @@ -0,0 +1,889 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef STICK10_COMMANDS_H +#define STICK10_COMMANDS_H + +#include <bitset> +#include <iomanip> +#include <string> +#include <sstream> +#include <stdint.h> +#include "device_proto.h" +#include "command.h" + +#pragma pack (push,1) + +namespace nitrokey { +namespace proto { + + + +/* + * Stick10 protocol definition + */ +namespace stick10 { +class GetSlotName : public Command<CommandID::READ_SLOT_NAME> { + public: + // reachable as a typedef in Transaction + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return slot_number<0x10+3; } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + uint8_t slot_name[15]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(slot_name); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, + struct ResponsePayload> CommandTransaction; +}; + +class EraseSlot : Command<CommandID::ERASE_SLOT> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class SetTime : Command<CommandID::SET_TIME> { + public: + struct CommandPayload { + uint8_t reset; // 0 - get time, 1 - set time + uint64_t time; // posix time + + bool isValid() const { return reset && reset != 1; } + std::string dissect() const { + std::stringstream ss; + ss << "reset:\t" << (int)(reset) << std::endl; + ss << "time:\t" << (time) << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + + +class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> { + public: + struct CommandPayload { + uint8_t slot_number; + uint8_t slot_name[15]; + uint8_t slot_secret[20]; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + union{ + uint8_t slot_token_id[13]; /** OATH Token Identifier */ + struct{ /** @see https://openauthentication.org/token-specs/ */ + uint8_t omp[2]; + uint8_t tt[2]; + uint8_t mui[8]; + uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805 + } slot_token_fields; + }; + union{ + uint64_t slot_counter; + uint8_t slot_counter_s[8]; + } __packed; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + print_to_ss_volatile(slot_name); + print_to_ss_volatile(slot_secret); + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + + ss << "slot_token_id:\t"; + for (auto i : slot_token_id) + ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ; + ss << std::endl; + ss << "slot_counter:\t[" << (int)slot_counter << "]\t" + << ::nitrokey::misc::hexdump((const uint8_t *)(&slot_counter), sizeof slot_counter, false); + + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class WriteToTOTPSlot : Command<CommandID::WRITE_TO_SLOT> { + public: + struct CommandPayload { + uint8_t slot_number; + uint8_t slot_name[15]; + uint8_t slot_secret[20]; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + union{ + uint8_t slot_token_id[13]; /** OATH Token Identifier */ + struct{ /** @see https://openauthentication.org/token-specs/ */ + uint8_t omp[2]; + uint8_t tt[2]; + uint8_t mui[8]; + uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805 + } slot_token_fields; + }; + uint16_t slot_interval; + + bool isValid() const { return !(slot_number & 0xF0); } //TODO check + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + print_to_ss_volatile(slot_name); + print_to_ss_volatile(slot_secret); + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "slot_token_id:\t"; + for (auto i : slot_token_id) + ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ; + ss << std::endl; + ss << "slot_interval:\t" << (int)slot_interval << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class GetTOTP : Command<CommandID::GET_CODE> { + public: + struct CommandPayload { + uint8_t slot_number; + uint64_t challenge; + uint64_t last_totp_time; + uint8_t last_interval; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + ss << "challenge:\t" << (challenge) << std::endl; + ss << "last_totp_time:\t" << (last_totp_time) << std::endl; + ss << "last_interval:\t" << (int)(last_interval) << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + union { + uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 + struct { + uint32_t code; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + } __packed ; + } __packed ; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << "code:\t" << (code) << std::endl; + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> + CommandTransaction; +}; + +class GetHOTP : Command<CommandID::GET_CODE> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return (slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + union { + uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 + struct { + uint32_t code; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + } __packed; + } __packed; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << "code:\t" << (code) << std::endl; + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> + CommandTransaction; +}; + +class ReadSlot : Command<CommandID::READ_SLOT> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return !(slot_number & 0xF0); } + + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + uint8_t slot_name[15]; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + union{ + uint8_t slot_token_id[13]; /** OATH Token Identifier */ + struct{ /** @see https://openauthentication.org/token-specs/ */ + uint8_t omp[2]; + uint8_t tt[2]; + uint8_t mui[8]; + uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805 + } slot_token_fields; + }; + union{ + uint64_t slot_counter; + uint8_t slot_counter_s[8]; + } __packed; + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(slot_name); + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + + ss << "slot_token_id:\t"; + for (auto i : slot_token_id) + ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ; + ss << std::endl; + ss << "slot_counter:\t[" << (int)slot_counter << "]\t" + << ::nitrokey::misc::hexdump((const uint8_t *)(&slot_counter), sizeof slot_counter, false); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, + struct ResponsePayload> CommandTransaction; +}; + +class GetStatus : Command<CommandID::GET_STATUS> { + public: + struct ResponsePayload { + union { + uint16_t firmware_version; + struct { + uint8_t minor; + uint8_t major; + } firmware_version_st; + }; + union{ + uint8_t card_serial[4]; + uint32_t card_serial_u32; + } __packed; + union { + uint8_t general_config[5]; + struct{ + uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */ + uint8_t capslock; /** same as numlock */ + uint8_t scrolllock; /** same as numlock */ + uint8_t enable_user_password; + uint8_t delete_user_password; /* unused */ + } __packed; + } __packed; + + static constexpr uint8_t special_HOTP_slots = 2; + bool isValid() const { return numlock < special_HOTP_slots && capslock < special_HOTP_slots + && scrolllock < special_HOTP_slots && enable_user_password < 2; } + + std::string get_card_serial_hex() const { + return nitrokey::misc::toHex(card_serial_u32); + } + + std::string dissect() const { + std::stringstream ss; + ss << "firmware_version:\t" + << "[" << firmware_version << "]" << "\t" + << ::nitrokey::misc::hexdump( + (const uint8_t *)(&firmware_version), sizeof firmware_version, false); + ss << "card_serial_u32:\t" << std::hex << card_serial_u32 << std::endl; + ss << "card_serial:\t" + << ::nitrokey::misc::hexdump((const uint8_t *)(card_serial), + sizeof card_serial, false); + ss << "general_config:\t" + << ::nitrokey::misc::hexdump((const uint8_t *)(general_config), + sizeof general_config, false); + ss << "numlock:\t" << (int)numlock << std::endl; + ss << "capslock:\t" << (int)capslock << std::endl; + ss << "scrolllock:\t" << (int)scrolllock << std::endl; + ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl; + ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl; + + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> + CommandTransaction; +}; + +class GetPasswordRetryCount : Command<CommandID::GET_PASSWORD_RETRY_COUNT> { + public: + struct ResponsePayload { + uint8_t password_retry_count; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << " password_retry_count\t" << (int)password_retry_count << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> + CommandTransaction; +}; + +class GetUserPasswordRetryCount + : Command<CommandID::GET_USER_PASSWORD_RETRY_COUNT> { + public: + struct ResponsePayload { + uint8_t password_retry_count; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << " password_retry_count\t" << (int)password_retry_count << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> + CommandTransaction; +}; + + template <typename T, typename Q, int N> + void write_array(T &ss, Q (&arr)[N]){ + for (int i=0; i<N; i++){ + ss << std::hex << std::setfill('0') << std::setw(2) << (int)arr[i] << " "; + } + ss << std::endl; + }; + + +class GetPasswordSafeSlotStatus : Command<CommandID::GET_PW_SAFE_SLOT_STATUS> { + public: + struct ResponsePayload { + uint8_t password_safe_status[PWS_SLOT_COUNT]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << "password_safe_status\t"; + write_array(ss, password_safe_status); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> + CommandTransaction; +}; + +class GetPasswordSafeSlotName : Command<CommandID::GET_PW_SAFE_SLOT_NAME> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number\t" << (int)slot_number << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + uint8_t slot_name[PWS_SLOTNAME_LENGTH]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(slot_name); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, + struct ResponsePayload> CommandTransaction; +}; + +class GetPasswordSafeSlotPassword + : Command<CommandID::GET_PW_SAFE_SLOT_PASSWORD> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << " slot_number\t" << (int)slot_number << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + uint8_t slot_password[PWS_PASSWORD_LENGTH]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(slot_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, + struct ResponsePayload> CommandTransaction; +}; + +class GetPasswordSafeSlotLogin + : Command<CommandID::GET_PW_SAFE_SLOT_LOGINNAME> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << " slot_number\t" << (int)slot_number << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + uint8_t slot_login[PWS_LOGINNAME_LENGTH]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(slot_login); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, + struct ResponsePayload> CommandTransaction; +}; + +class SetPasswordSafeSlotData : Command<CommandID::SET_PW_SAFE_SLOT_DATA_1> { + public: + struct CommandPayload { + uint8_t slot_number; + uint8_t slot_name[PWS_SLOTNAME_LENGTH]; + uint8_t slot_password[PWS_PASSWORD_LENGTH]; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << " slot_number\t" << (int)slot_number << std::endl; + print_to_ss_volatile(slot_name); + print_to_ss_volatile(slot_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class SetPasswordSafeSlotData2 : Command<CommandID::SET_PW_SAFE_SLOT_DATA_2> { + public: + struct CommandPayload { + uint8_t slot_number; + uint8_t slot_login_name[PWS_LOGINNAME_LENGTH]; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << " slot_number\t" << (int)slot_number << std::endl; + print_to_ss_volatile(slot_login_name); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class ErasePasswordSafeSlot : Command<CommandID::PW_SAFE_ERASE_SLOT> { + public: + struct CommandPayload { + uint8_t slot_number; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << " slot_number\t" << (int)slot_number << std::endl; + return ss.str(); + } + + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class EnablePasswordSafe : Command<CommandID::PW_SAFE_ENABLE> { + public: + struct CommandPayload { + uint8_t user_password[30]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(user_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class PasswordSafeInitKey : Command<CommandID::PW_SAFE_INIT_KEY> { + /** + * never used in Nitrokey App + */ + public: + typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload> + CommandTransaction; +}; + +class PasswordSafeSendSlotViaHID : Command<CommandID::PW_SAFE_SEND_DATA> { + /** + * never used in Nitrokey App + */ + public: + struct CommandPayload { + uint8_t slot_number; + uint8_t slot_kind; + + bool isValid() const { return !(slot_number & 0xF0); } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +// TODO "Device::passwordSafeSendSlotDataViaHID" + +class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> { + public: + struct CommandPayload { + union{ + uint8_t config[5]; + struct{ + uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */ + uint8_t capslock; /** same as numlock */ + uint8_t scrolllock; /** same as numlock */ + uint8_t enable_user_password; + uint8_t delete_user_password; + }; + }; + bool isValid() const { return numlock < 2 && capslock < 2 && scrolllock < 2 && enable_user_password < 2; } + + std::string dissect() const { + std::stringstream ss; + ss << "numlock:\t" << (int)numlock << std::endl; + ss << "capslock:\t" << (int)capslock << std::endl; + ss << "scrolllock:\t" << (int)scrolllock << std::endl; + ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl; + ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class FirstAuthenticate : Command<CommandID::FIRST_AUTHENTICATE> { + public: + struct CommandPayload { + uint8_t card_password[25]; + uint8_t temporary_password[25]; + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(card_password); + hexdump_to_ss(temporary_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class UserAuthenticate : Command<CommandID::USER_AUTHENTICATE> { + public: + struct CommandPayload { + uint8_t card_password[25]; + uint8_t temporary_password[25]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(card_password); + hexdump_to_ss(temporary_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class Authorize : Command<CommandID::AUTHORIZE> { + public: + struct CommandPayload { + uint32_t crc_to_authorize; + uint8_t temporary_password[25]; + + std::string dissect() const { + std::stringstream ss; + ss << " crc_to_authorize:\t" << std::hex << std::setw(2) << std::setfill('0') << crc_to_authorize<< std::endl; + hexdump_to_ss(temporary_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class UserAuthorize : Command<CommandID::USER_AUTHORIZE> { + public: + struct CommandPayload { + uint32_t crc_to_authorize; + uint8_t temporary_password[25]; + std::string dissect() const { + std::stringstream ss; + ss << " crc_to_authorize:\t" << crc_to_authorize<< std::endl; + hexdump_to_ss(temporary_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> { + public: + struct CommandPayload { + uint8_t admin_password[25]; + uint8_t user_new_password[25]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(admin_password); + print_to_ss_volatile(user_new_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class ChangeUserPin : Command<CommandID::CHANGE_USER_PIN> { + public: + struct CommandPayload { + uint8_t old_pin[25]; + uint8_t new_pin[25]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(old_pin); + print_to_ss_volatile(new_pin); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class IsAESSupported : Command<CommandID::DETECT_SC_AES> { + public: + struct CommandPayload { + uint8_t user_password[20]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(user_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + + +class ChangeAdminPin : Command<CommandID::CHANGE_ADMIN_PIN> { + public: + struct CommandPayload { + uint8_t old_pin[25]; + uint8_t new_pin[25]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(old_pin); + print_to_ss_volatile(new_pin); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class LockDevice : Command<CommandID::LOCK_DEVICE> { + public: + typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload> + CommandTransaction; +}; + +class FactoryReset : Command<CommandID::FACTORY_RESET> { + public: + struct CommandPayload { + uint8_t admin_password[20]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(admin_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; +}; + +class BuildAESKey : Command<CommandID::NEW_AES_KEY> { + public: + struct CommandPayload { + uint8_t admin_password[20]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile(admin_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + +}; + +} +} +} +#pragma pack (pop) +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands_0.8.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands_0.8.h new file mode 100644 index 0000000..9477890 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick10_commands_0.8.h @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + + +#ifndef LIBNITROKEY_STICK10_COMMANDS_0_8_H +#define LIBNITROKEY_STICK10_COMMANDS_0_8_H + +#include <bitset> +#include <iomanip> +#include <string> +#include <sstream> +#include <cstdint> +#include "command.h" +#include "device_proto.h" +#include "stick10_commands.h" + +#pragma pack (push,1) + + +namespace nitrokey { + namespace proto { + +/* + * Stick10 protocol definition + */ + namespace stick10_08 { + using stick10::FirstAuthenticate; + using stick10::UserAuthenticate; + using stick10::SetTime; + using stick10::GetStatus; + using stick10::BuildAESKey; + using stick10::ChangeAdminPin; + using stick10::ChangeUserPin; + using stick10::EnablePasswordSafe; + using stick10::ErasePasswordSafeSlot; + using stick10::FactoryReset; + using stick10::GetPasswordRetryCount; + using stick10::GetUserPasswordRetryCount; + using stick10::GetPasswordSafeSlotLogin; + using stick10::GetPasswordSafeSlotName; + using stick10::GetPasswordSafeSlotPassword; + using stick10::GetPasswordSafeSlotStatus; + using stick10::GetSlotName; + using stick10::IsAESSupported; + using stick10::LockDevice; + using stick10::PasswordSafeInitKey; + using stick10::PasswordSafeSendSlotViaHID; + using stick10::SetPasswordSafeSlotData; + using stick10::SetPasswordSafeSlotData2; + using stick10::UnlockUserPassword; + using stick10::ReadSlot; + + class EraseSlot : Command<CommandID::ERASE_SLOT> { + public: + struct CommandPayload { + uint8_t slot_number; + uint8_t temporary_admin_password[25]; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + hexdump_to_ss(temporary_admin_password); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + }; + + class SendOTPData : Command<CommandID::SEND_OTP_DATA> { + //admin auth + public: + struct CommandPayload { + uint8_t temporary_admin_password[25]; + uint8_t type; //S-secret, N-name + uint8_t id; //multiple reports for values longer than 30 bytes + uint8_t data[30]; //data, does not need null termination + + bool isValid() const { return true; } + + void setTypeName(){ + type = 'N'; + } + void setTypeSecret(){ + type = 'S'; + } + + std::string dissect() const { + std::stringstream ss; + hexdump_to_ss(temporary_admin_password); + ss << "type:\t" << type << std::endl; + ss << "id:\t" << (int)id << std::endl; +#ifdef LOG_VOLATILE_DATA + ss << "data:" << std::endl + << ::nitrokey::misc::hexdump((const uint8_t *) (&data), sizeof data); +#else + ss << " Volatile data not logged" << std::endl; +#endif + return ss.str(); + } + } __packed; + + + struct ResponsePayload { + union { + uint8_t data[40]; + } __packed; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; +#ifdef LOG_VOLATILE_DATA + ss << "data:" << std::endl + << ::nitrokey::misc::hexdump((const uint8_t *) (&data), sizeof data); +#else + ss << " Volatile data not logged" << std::endl; +#endif + return ss.str(); + } + } __packed; + + + typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> + CommandTransaction; + }; + + class WriteToOTPSlot : Command<CommandID::WRITE_TO_SLOT> { + //admin auth + public: + struct CommandPayload { + uint8_t temporary_admin_password[25]; + uint8_t slot_number; + union { + uint64_t slot_counter_or_interval; + uint8_t slot_counter_s[8]; + } __packed; + union { + uint8_t _slot_config; + struct { + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + union { + uint8_t slot_token_id[13]; /** OATH Token Identifier */ + struct { /** @see https://openauthentication.org/token-specs/ */ + uint8_t omp[2]; + uint8_t tt[2]; + uint8_t mui[8]; + uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805 + } slot_token_fields; + }; + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + hexdump_to_ss(temporary_admin_password); + ss << "slot_config:\t" << std::bitset<8>((int) _slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + ss << "slot_number:\t" << (int) (slot_number) << std::endl; + ss << "slot_counter_or_interval:\t[" << (int) slot_counter_or_interval << "]\t" + << ::nitrokey::misc::hexdump((const uint8_t *) (&slot_counter_or_interval), sizeof slot_counter_or_interval, false); + + ss << "slot_token_id:\t"; + for (auto i : slot_token_id) + ss << std::hex << std::setw(2) << std::setfill('0') << (int) i << " "; + ss << std::endl; + + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + }; + + class GetHOTP : Command<CommandID::GET_CODE> { + public: + struct CommandPayload { + uint8_t slot_number; + struct { + uint64_t challenge; //@unused + uint64_t last_totp_time; //@unused + uint8_t last_interval; //@unused + } __packed _unused; + uint8_t temporary_user_password[25]; + + bool isValid() const { return (slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + hexdump_to_ss(temporary_user_password); + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + union { + uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 + struct { + uint32_t code; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + } __packed; + } __packed; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << "code:\t" << (code) << std::endl; + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> + CommandTransaction; + }; + + + class GetTOTP : Command<CommandID::GET_CODE> { + //user auth + public: + struct CommandPayload { + uint8_t slot_number; + uint64_t challenge; //@unused + uint64_t last_totp_time; //@unused + uint8_t last_interval; //@unused + uint8_t temporary_user_password[25]; + + bool isValid() const { return !(slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + hexdump_to_ss(temporary_user_password); + ss << "slot_number:\t" << (int)(slot_number) << std::endl; + ss << "challenge:\t" << (challenge) << std::endl; + ss << "last_totp_time:\t" << (last_totp_time) << std::endl; + ss << "last_interval:\t" << (int)(last_interval) << std::endl; + return ss.str(); + } + } __packed; + + struct ResponsePayload { + union { + uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 + struct { + uint32_t code; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; + } __packed ; + } __packed ; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << "code:\t" << (code) << std::endl; + ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; + ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; + ss << "\tuse_enter(1):\t" << use_enter << std::endl; + ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> + CommandTransaction; + }; + + + class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> { + //admin auth + public: + struct CommandPayload { + union{ + uint8_t config[5]; + struct{ + uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */ + uint8_t capslock; /** same as numlock */ + uint8_t scrolllock; /** same as numlock */ + uint8_t enable_user_password; + uint8_t delete_user_password; + }; + }; + uint8_t temporary_admin_password[25]; + + static constexpr uint8_t special_HOTP_slots = 3; + bool isValid() const { return numlock < special_HOTP_slots && capslock < special_HOTP_slots + && scrolllock < special_HOTP_slots && enable_user_password < 2; } + + std::string dissect() const { + std::stringstream ss; + ss << "numlock:\t" << (int)numlock << std::endl; + ss << "capslock:\t" << (int)capslock << std::endl; + ss << "scrolllock:\t" << (int)scrolllock << std::endl; + ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl; + ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl; + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + }; + } + } +} +#pragma pack (pop) + +#endif //LIBNITROKEY_STICK10_COMMANDS_0_8_H diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick20_commands.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick20_commands.h new file mode 100644 index 0000000..7efa1b6 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/stick20_commands.h @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef STICK20_COMMANDS_H +#define STICK20_COMMANDS_H + + + +#include <cstdint> +#include "command.h" +#include <string> +#include <sstream> +#include "device_proto.h" + +#pragma pack (push,1) + +namespace nitrokey { + namespace proto { + +/* +* STICK20 protocol command ids +* a superset (almost) of STICK10 +*/ + + namespace stick20 { + + class ChangeAdminUserPin20Current : + public PasswordCommand<CommandID::SEND_PASSWORD, PasswordKind::Admin> {}; + class ChangeAdminUserPin20New : + public PasswordCommand<CommandID::SEND_NEW_PASSWORD, PasswordKind::Admin> {}; + class UnlockUserPin : + public PasswordCommand<CommandID::UNLOCK_USER_PASSWORD, PasswordKind::Admin> {}; + + class EnableEncryptedPartition : public PasswordCommand<CommandID::ENABLE_CRYPTED_PARI> {}; + class EnableHiddenEncryptedPartition : public PasswordCommand<CommandID::ENABLE_HIDDEN_CRYPTED_PARI> {}; + + class SetUnencryptedVolumeReadOnlyAdmin : + public PasswordCommand<CommandID::ENABLE_ADMIN_READONLY_UNCRYPTED_LUN, PasswordKind::Admin> {}; + class SetUnencryptedVolumeReadWriteAdmin : + public PasswordCommand<CommandID::ENABLE_ADMIN_READWRITE_UNCRYPTED_LUN, PasswordKind::Admin> {}; + class SetEncryptedVolumeReadOnly : + public PasswordCommand<CommandID::ENABLE_ADMIN_READONLY_ENCRYPTED_LUN, PasswordKind::Admin> {}; + class SetEncryptedVolumeReadWrite : + public PasswordCommand<CommandID::ENABLE_ADMIN_READWRITE_ENCRYPTED_LUN, PasswordKind::Admin> {}; + + //FIXME the volume disabling commands do not need password + class DisableEncryptedPartition : public PasswordCommand<CommandID::DISABLE_CRYPTED_PARI> {}; + class DisableHiddenEncryptedPartition : public PasswordCommand<CommandID::DISABLE_HIDDEN_CRYPTED_PARI> {}; + + class EnableFirmwareUpdate : public PasswordCommand<CommandID::ENABLE_FIRMWARE_UPDATE> {}; + + class ChangeUpdatePassword : Command<CommandID::CHANGE_UPDATE_PIN> { + public: + struct CommandPayload { + uint8_t __gap; + uint8_t current_update_password[20]; + uint8_t __gap2; + uint8_t new_update_password[20]; + std::string dissect() const { + std::stringstream ss; + print_to_ss_volatile( current_update_password ); + print_to_ss_volatile( new_update_password ); + return ss.str(); + } + }; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + }; + + class ExportFirmware : public PasswordCommand<CommandID::EXPORT_FIRMWARE_TO_FILE> {}; + + class CreateNewKeys : + public PasswordCommand<CommandID::GENERATE_NEW_KEYS, PasswordKind::AdminPrefixed, 30> {}; + + + class FillSDCardWithRandomChars : Command<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS> { + public: + enum class ChosenVolumes : uint8_t { + all_volumes = 0, + encrypted_volume = 1 + }; + + struct CommandPayload { + uint8_t volume_flag; + uint8_t kind; + uint8_t admin_pin[20]; + + std::string dissect() const { + std::stringstream ss; + print_to_ss( (int) volume_flag ); + print_to_ss( kind ); + print_to_ss_volatile(admin_pin); + return ss.str(); + } + void set_kind_user() { + kind = (uint8_t) 'P'; + } + void set_defaults(){ + set_kind_user(); + volume_flag = static_cast<uint8_t>(ChosenVolumes::encrypted_volume); + } + + } __packed; + + typedef Transaction<Command<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS>::command_id(), + struct CommandPayload, struct EmptyPayload> + CommandTransaction; + }; + + namespace StorageCommandResponsePayload{ + using namespace DeviceResponseConstants; + static constexpr auto padding_size = + storage_data_absolute_address - header_size; + struct TransmissionData{ + uint8_t _padding[padding_size]; + + uint8_t SendCounter_u8; + uint8_t SendDataType_u8; + uint8_t FollowBytesFlag_u8; + uint8_t SendSize_u8; + + std::string dissect() const { + std::stringstream ss; + ss << "_padding:" << std::endl + << ::nitrokey::misc::hexdump((const uint8_t *) (_padding), + sizeof _padding); + print_to_ss((int) SendCounter_u8); + print_to_ss((int) SendDataType_u8); + print_to_ss((int) FollowBytesFlag_u8); + print_to_ss((int) SendSize_u8); + return ss.str(); + } + + } __packed; + } + + namespace DeviceConfigurationResponsePacket{ + + struct ResponsePayload { + StorageCommandResponsePayload::TransmissionData transmission_data; + + uint16_t MagicNumber_StickConfig_u16; + /** + * READ_WRITE_ACTIVE = ReadWriteFlagUncryptedVolume_u8 == 0; + */ + uint8_t ReadWriteFlagUncryptedVolume_u8; + uint8_t ReadWriteFlagCryptedVolume_u8; + + union{ + uint8_t VersionInfo_au8[4]; + struct { + uint8_t major; + uint8_t minor; + uint8_t _reserved2; + uint8_t build_iteration; + } __packed versionInfo; + } __packed; + + uint8_t ReadWriteFlagHiddenVolume_u8; + uint8_t FirmwareLocked_u8; + + union{ + uint8_t NewSDCardFound_u8; + struct { + bool NewCard :1; + uint8_t Counter :7; + } __packed NewSDCardFound_st; + } __packed; + + /** + * SD card FILLED with random chars + */ + uint8_t SDFillWithRandomChars_u8; + uint32_t ActiveSD_CardID_u32; + union{ + uint8_t VolumeActiceFlag_u8; + struct { + bool unencrypted :1; + bool encrypted :1; + bool hidden :1; + } __packed VolumeActiceFlag_st; + } __packed; + uint8_t NewSmartCardFound_u8; + uint8_t UserPwRetryCount; + uint8_t AdminPwRetryCount; + uint32_t ActiveSmartCardID_u32; + uint8_t StickKeysNotInitiated; + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + + print_to_ss(transmission_data.dissect()); + print_to_ss( MagicNumber_StickConfig_u16 ); + print_to_ss((int) ReadWriteFlagUncryptedVolume_u8 ); + print_to_ss((int) ReadWriteFlagCryptedVolume_u8 ); + print_to_ss((int) ReadWriteFlagHiddenVolume_u8 ); + print_to_ss((int) versionInfo.major ); + print_to_ss((int) versionInfo.minor ); + print_to_ss((int) versionInfo.build_iteration ); + print_to_ss((int) FirmwareLocked_u8 ); + print_to_ss((int) NewSDCardFound_u8 ); + print_to_ss((int) NewSDCardFound_st.NewCard ); + print_to_ss((int) NewSDCardFound_st.Counter ); + print_to_ss((int) SDFillWithRandomChars_u8 ); + print_to_ss( ActiveSD_CardID_u32 ); + print_to_ss((int) VolumeActiceFlag_u8 ); + print_to_ss((int) VolumeActiceFlag_st.unencrypted ); + print_to_ss((int) VolumeActiceFlag_st.encrypted ); + print_to_ss((int) VolumeActiceFlag_st.hidden); + print_to_ss((int) NewSmartCardFound_u8 ); + print_to_ss((int) UserPwRetryCount ); + print_to_ss((int) AdminPwRetryCount ); + print_to_ss( ActiveSmartCardID_u32 ); + print_to_ss((int) StickKeysNotInitiated ); + + return ss.str(); + } + } __packed; + } + + class SendStartup : Command<CommandID::SEND_STARTUP> { + public: + struct CommandPayload { + uint64_t localtime; // POSIX seconds from epoch start, supports until year 2106 + std::string dissect() const { + std::stringstream ss; + print_to_ss( localtime ); + return ss.str(); + } + void set_defaults(){ + localtime = + std::chrono::duration_cast<std::chrono::seconds> ( + std::chrono::system_clock::now().time_since_epoch()).count(); + } + }__packed; + + using ResponsePayload = DeviceConfigurationResponsePacket::ResponsePayload; + + typedef Transaction<command_id(), struct CommandPayload, ResponsePayload> + CommandTransaction; + }; + + +// TODO fix original nomenclature + class SendSetReadonlyToUncryptedVolume : public PasswordCommand<CommandID::ENABLE_READONLY_UNCRYPTED_LUN> {}; + class SendSetReadwriteToUncryptedVolume : public PasswordCommand<CommandID::ENABLE_READWRITE_UNCRYPTED_LUN> {}; + class SendClearNewSdCardFound : public PasswordCommand<CommandID::CLEAR_NEW_SD_CARD_FOUND> {}; + + class GetDeviceStatus : Command<CommandID::GET_DEVICE_STATUS> { + public: + using ResponsePayload = DeviceConfigurationResponsePacket::ResponsePayload; + + typedef Transaction<command_id(), struct EmptyPayload, ResponsePayload> + CommandTransaction; + }; + + class Wink : Command<CommandID::WINK> { + public: + typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload> + CommandTransaction; + }; + + class CheckSmartcardUsage : Command<CommandID::CHECK_SMARTCARD_USAGE> { + public: + typedef Transaction<command_id(), struct EmptyPayload, EmptyPayload> + CommandTransaction; + }; + + class GetSDCardOccupancy : Command<CommandID::SD_CARD_HIGH_WATERMARK> { + public: + struct ResponsePayload { + uint8_t WriteLevelMin; + uint8_t WriteLevelMax; + uint8_t ReadLevelMin; + uint8_t ReadLevelMax; + std::string dissect() const { + std::stringstream ss; + print_to_ss((int) WriteLevelMin); + print_to_ss((int) WriteLevelMax); + print_to_ss((int) ReadLevelMin); + print_to_ss((int) ReadLevelMax); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> + CommandTransaction; + }; + + + class SetupHiddenVolume : Command<CommandID::SEND_HIDDEN_VOLUME_SETUP> { + public: + constexpr static int MAX_HIDDEN_VOLUME_PASSWORD_SIZE = 20; + struct CommandPayload { + uint8_t SlotNr_u8; + uint8_t StartBlockPercent_u8; + uint8_t EndBlockPercent_u8; + uint8_t HiddenVolumePassword_au8[MAX_HIDDEN_VOLUME_PASSWORD_SIZE]; + std::string dissect() const { + std::stringstream ss; + print_to_ss((int) SlotNr_u8); + print_to_ss((int) StartBlockPercent_u8); + print_to_ss((int) EndBlockPercent_u8); + print_to_ss_volatile(HiddenVolumePassword_au8); + return ss.str(); + } + } __packed; + + typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> + CommandTransaction; + }; + + +//disable this command for now +// class LockFirmware : public PasswordCommand<CommandID::SEND_LOCK_STICK_HARDWARE> {}; + + class ProductionTest : Command<CommandID::PRODUCTION_TEST> { + public: + struct ResponsePayload { + + StorageCommandResponsePayload::TransmissionData transmission_data; + + uint8_t FirmwareVersion_au8[2]; // 2 byte // 2 + uint8_t FirmwareVersionInternal_u8; // 1 byte // 3 + uint8_t SD_Card_Size_u8; // 1 byte // 4 + uint32_t CPU_CardID_u32; // 4 byte // 8 + uint32_t SmartCardID_u32; // 4 byte // 12 + uint32_t SD_CardID_u32; // 4 byte // 16 + uint8_t SC_UserPwRetryCount; // User PIN retry count 1 byte // 17 + uint8_t SC_AdminPwRetryCount; // Admin PIN retry count 1 byte // 18 + uint8_t SD_Card_ManufacturingYear_u8; // 1 byte // 19 + uint8_t SD_Card_ManufacturingMonth_u8; // 1 byte // 20 + uint16_t SD_Card_OEM_u16; // 2 byte // 22 + uint16_t SD_WriteSpeed_u16; // in kbyte / sec 2 byte // 24 + uint8_t SD_Card_Manufacturer_u8; // 1 byte // 25 + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + + print_to_ss(transmission_data.dissect()); + print_to_ss((int) FirmwareVersion_au8[0]); + print_to_ss((int) FirmwareVersion_au8[1]); + print_to_ss((int) FirmwareVersionInternal_u8); + print_to_ss((int) SD_Card_Size_u8); + print_to_ss( CPU_CardID_u32); + print_to_ss( SmartCardID_u32); + print_to_ss( SD_CardID_u32); + print_to_ss((int) SC_UserPwRetryCount); + print_to_ss((int) SC_AdminPwRetryCount); + print_to_ss((int) SD_Card_ManufacturingYear_u8); + print_to_ss((int) SD_Card_ManufacturingMonth_u8); + print_to_ss( SD_Card_OEM_u16); + print_to_ss( SD_WriteSpeed_u16); + print_to_ss((int) SD_Card_Manufacturer_u8); + return ss.str(); + } + + } __packed; + + typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> + CommandTransaction; + }; + + } + } +} + +#undef print_to_ss +#pragma pack (pop) + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/version.h b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/version.h new file mode 100644 index 0000000..6547af0 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/libnitrokey/version.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#ifndef LIBNITROKEY_VERSION_H +#define LIBNITROKEY_VERSION_H + +namespace nitrokey { + unsigned int get_major_library_version(); + + unsigned int get_minor_library_version(); + + const char* get_library_version(); +} + +#endif diff --git a/nitrokey-sys/libnitrokey-v3.4.1/log.cc b/nitrokey-sys/libnitrokey-v3.4.1/log.cc new file mode 100644 index 0000000..06acee7 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/log.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include "log.h" +#include <iostream> +#include <ctime> +#include <iomanip> + +#include <sstream> + +namespace nitrokey { + namespace log { + + Log *Log::mp_instance = nullptr; + StdlogHandler stdlog_handler; + + std::string Log::prefix = ""; + + + std::string LogHandler::loglevel_to_str(Loglevel lvl) { + switch (lvl) { + case Loglevel::DEBUG_L1: + return std::string("DEBUG_L1"); + case Loglevel::DEBUG_L2: + return std::string("DEBUG_L2"); + case Loglevel::DEBUG: + return std::string("DEBUG"); + case Loglevel::INFO: + return std::string("INFO"); + case Loglevel::WARNING: + return std::string("WARNING"); + case Loglevel::ERROR: + return std::string("ERROR"); + } + return std::string(""); + } + + void Log::operator()(const std::string &logstr, Loglevel lvl) { + if (mp_loghandler != nullptr){ + if ((int) lvl <= (int) m_loglevel) mp_loghandler->print(prefix+logstr, lvl); + } + } + + void Log::setPrefix(const std::string prefix) { + if (!prefix.empty()){ + Log::prefix = "["+prefix+"]"; + } else { + Log::prefix = ""; + } + } + + void StdlogHandler::print(const std::string &str, Loglevel lvl) { + std::string s = format_message_to_string(str, lvl); + std::clog << s; + } + + void FunctionalLogHandler::print(const std::string &str, Loglevel lvl) { + std::string s = format_message_to_string(str, lvl); + log_function(s); + } + + std::string LogHandler::format_message_to_string(const std::string &str, const Loglevel &lvl) { + static bool last_short = false; + if (str.length() == 1){ + last_short = true; + return str; + } + time_t t = time(nullptr); + tm tm = *localtime(&t); + + std::stringstream s; + s + << (last_short? "\n" : "") + << "[" << std::put_time(&tm, "%c") << "]" + << "[" << loglevel_to_str(lvl) << "]\t" + << str << std::endl; + last_short = false; + return s.str(); + } + + FunctionalLogHandler::FunctionalLogHandler(log_function_type _log_function) { + log_function = _log_function; + } + } +} diff --git a/nitrokey-sys/libnitrokey-v3.4.1/misc.cc b/nitrokey-sys/libnitrokey-v3.4.1/misc.cc new file mode 100644 index 0000000..59185f3 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/misc.cc @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015-2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include <sstream> +#include <string> +#include "misc.h" +#include "inttypes.h" +#include <cstdlib> +#include <cstring> +#include "LibraryException.h" +#include <vector> + +namespace nitrokey { +namespace misc { + + + +::std::vector<uint8_t> hex_string_to_byte(const char* hexString){ + const size_t big_string_size = 257; //arbitrary 'big' number + const size_t s_size = strnlen(hexString, big_string_size); + const size_t d_size = s_size/2; + if (s_size%2!=0 || s_size>=big_string_size){ + throw InvalidHexString(0); + } + auto data = ::std::vector<uint8_t>(); + data.reserve(d_size); + + char buf[3]; + buf[2] = '\0'; + for(size_t i=0; i<s_size; i++){ + + char c = hexString[i]; + bool char_from_range = (('0' <= c && c <='9') || ('A' <= c && c <= 'F') || ('a' <= c && c<= 'f')); + if (!char_from_range){ + throw InvalidHexString(c); + } + buf[i%2] = c; + if (i%2==1){ + data.push_back( strtoul(buf, NULL, 16) & 0xFF ); + } + } + return data; +}; + +#include <cctype> +::std::string hexdump(const uint8_t *p, size_t size, bool print_header, + bool print_ascii, bool print_empty) { + ::std::stringstream out; + char formatbuf[128]; + const uint8_t *pstart = p; + + for (const uint8_t *pend = p + size; p < pend;) { + if (print_header){ + snprintf(formatbuf, 128, "%04x\t", static_cast<int> (p - pstart)); + out << formatbuf; + } + + const uint8_t* pp = p; + for (const uint8_t *le = p + 16; p < le; p++) { + if (p < pend){ + snprintf(formatbuf, 128, "%02x ", uint8_t(*p)); + out << formatbuf; + } else { + if(print_empty) + out << "-- "; + } + + } + if(print_ascii){ + out << " "; + for (const uint8_t *le = pp + 16; pp < le && pp < pend; pp++) { + if (std::isgraph(*pp)) + out << uint8_t(*pp); + else + out << '.'; + } + } + out << ::std::endl; + } + return out.str(); +} + +static uint32_t _crc32(uint32_t crc, uint32_t data) { + int i; + crc = crc ^ data; + + for (i = 0; i < 32; i++) { + if (crc & 0x80000000) + crc = (crc << 1) ^ 0x04C11DB7; // polynomial used in STM32 + else + crc = (crc << 1); + } + + return crc; +} + +uint32_t stm_crc32(const uint8_t *data, size_t size) { + uint32_t crc = 0xffffffff; + const uint32_t *pend = (const uint32_t *)(data + size); + for (const uint32_t *p = (const uint32_t *)(data); p < pend; p++) + crc = _crc32(crc, *p); + return crc; +} +} +} diff --git a/nitrokey-sys/libnitrokey-v3.4.1/version.cc b/nitrokey-sys/libnitrokey-v3.4.1/version.cc new file mode 100644 index 0000000..dfdc802 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/version.cc @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include "version.h" + +namespace nitrokey { + unsigned int get_major_library_version() { + return 3; + } + + unsigned int get_minor_library_version() { + return 0; + } + + const char* get_library_version() { + return "unknown"; + } +} + diff --git a/nitrokey-sys/libnitrokey-v3.4.1/version.cc.in b/nitrokey-sys/libnitrokey-v3.4.1/version.cc.in new file mode 100644 index 0000000..0eae647 --- /dev/null +++ b/nitrokey-sys/libnitrokey-v3.4.1/version.cc.in @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Nitrokey UG + * + * This file is part of libnitrokey. + * + * libnitrokey is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * libnitrokey is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-3.0 + */ + +#include "version.h" + +namespace nitrokey { + unsigned int get_major_library_version() { + return @PROJECT_VERSION_MAJOR@; + } + + unsigned int get_minor_library_version() { + return @PROJECT_VERSION_MINOR@; + } + + const char* get_library_version() { + return "@PROJECT_VERSION_GIT@"; + } +} + diff --git a/nitrokey-sys/src/ffi.rs b/nitrokey-sys/src/ffi.rs new file mode 100644 index 0000000..58879ad --- /dev/null +++ b/nitrokey-sys/src/ffi.rs @@ -0,0 +1,1112 @@ +/* automatically generated by rust-bindgen, manually modified */ + +/// Use, if no supported device is connected +pub const NK_device_model_NK_DISCONNECTED: NK_device_model = 0; +/// Nitrokey Pro. +pub const NK_device_model_NK_PRO: NK_device_model = 1; +/// Nitrokey Storage. +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; +/// Stores the status of a Storage device. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NK_storage_status { + /// Indicates whether the unencrypted volume is read-only. + pub unencrypted_volume_read_only: bool, + /// Indicates whether the unencrypted volume is active. + pub unencrypted_volume_active: bool, + /// Indicates whether the encrypted volume is read-only. + pub encrypted_volume_read_only: bool, + /// Indicates whether the encrypted volume is active. + pub encrypted_volume_active: bool, + /// Indicates whether the hidden volume is read-only. + pub hidden_volume_read_only: bool, + /// Indicates whether the hidden volume is active. + pub hidden_volume_active: bool, + /// 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, + /// Indicates whether the firmware is locked. + pub firmware_locked: bool, + /// The serial number of the SD card in the Storage stick. + pub serial_number_sd_card: u32, + /// The serial number of the smart card in the Storage stick. + pub serial_number_smart_card: u32, + /// The number of remaining login attempts for the user PIN. + pub user_retry_count: u8, + /// The number of remaining login attempts for the admin PIN. + pub admin_retry_count: u8, + /// Indicates whether a new SD card was found. + pub new_sd_card_found: bool, + /// Indicates whether the SD card is filled with random characters. + pub filled_with_random: bool, + /// Indicates whether the stick has been initialized by generating + /// the AES keys. + pub stick_initialized: bool, +} +#[test] +fn bindgen_test_layout_NK_storage_status() { + assert_eq!( + ::std::mem::size_of::<NK_storage_status>(), + 28usize, + concat!("Size of: ", stringify!(NK_storage_status)) + ); + assert_eq!( + ::std::mem::align_of::<NK_storage_status>(), + 4usize, + concat!("Alignment of ", stringify!(NK_storage_status)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).unencrypted_volume_read_only as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(unencrypted_volume_read_only) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).unencrypted_volume_active as *const _ + as usize + }, + 1usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(unencrypted_volume_active) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).encrypted_volume_read_only as *const _ + as usize + }, + 2usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(encrypted_volume_read_only) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).encrypted_volume_active as *const _ + as usize + }, + 3usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(encrypted_volume_active) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).hidden_volume_read_only as *const _ + as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(hidden_volume_read_only) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).hidden_volume_active as *const _ as usize + }, + 5usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(hidden_volume_active) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).firmware_version_major as *const _ + as usize + }, + 6usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(firmware_version_major) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).firmware_version_minor as *const _ + as usize + }, + 7usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(firmware_version_minor) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).firmware_locked as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(firmware_locked) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).serial_number_sd_card as *const _ as usize + }, + 12usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(serial_number_sd_card) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).serial_number_smart_card as *const _ + as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(serial_number_smart_card) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).user_retry_count as *const _ as usize + }, + 20usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(user_retry_count) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).admin_retry_count as *const _ as usize + }, + 21usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(admin_retry_count) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).new_sd_card_found as *const _ as usize + }, + 22usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(new_sd_card_found) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).filled_with_random as *const _ as usize + }, + 23usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(filled_with_random) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_status>())).stick_initialized as *const _ as usize + }, + 24usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_status), + "::", + stringify!(stick_initialized) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NK_storage_ProductionTest { + pub FirmwareVersion_au8: [u8; 2usize], + pub FirmwareVersionInternal_u8: u8, + pub SD_Card_Size_u8: u8, + pub CPU_CardID_u32: u32, + pub SmartCardID_u32: u32, + pub SD_CardID_u32: u32, + pub SC_UserPwRetryCount: u8, + pub SC_AdminPwRetryCount: u8, + pub SD_Card_ManufacturingYear_u8: u8, + pub SD_Card_ManufacturingMonth_u8: u8, + pub SD_Card_OEM_u16: u16, + pub SD_WriteSpeed_u16: u16, + pub SD_Card_Manufacturer_u8: u8, +} +#[test] +fn bindgen_test_layout_NK_storage_ProductionTest() { + assert_eq!( + ::std::mem::size_of::<NK_storage_ProductionTest>(), + 28usize, + concat!("Size of: ", stringify!(NK_storage_ProductionTest)) + ); + assert_eq!( + ::std::mem::align_of::<NK_storage_ProductionTest>(), + 4usize, + concat!("Alignment of ", stringify!(NK_storage_ProductionTest)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).FirmwareVersion_au8 as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(FirmwareVersion_au8) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).FirmwareVersionInternal_u8 + as *const _ as usize + }, + 2usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(FirmwareVersionInternal_u8) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_Card_Size_u8 as *const _ + as usize + }, + 3usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_Card_Size_u8) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).CPU_CardID_u32 as *const _ + as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(CPU_CardID_u32) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SmartCardID_u32 as *const _ + as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SmartCardID_u32) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_CardID_u32 as *const _ as usize + }, + 12usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_CardID_u32) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SC_UserPwRetryCount as *const _ + as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SC_UserPwRetryCount) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SC_AdminPwRetryCount as *const _ + as usize + }, + 17usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SC_AdminPwRetryCount) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_Card_ManufacturingYear_u8 + as *const _ as usize + }, + 18usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_Card_ManufacturingYear_u8) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_Card_ManufacturingMonth_u8 + as *const _ as usize + }, + 19usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_Card_ManufacturingMonth_u8) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_Card_OEM_u16 as *const _ + as usize + }, + 20usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_Card_OEM_u16) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_WriteSpeed_u16 as *const _ + as usize + }, + 22usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_WriteSpeed_u16) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<NK_storage_ProductionTest>())).SD_Card_Manufacturer_u8 + as *const _ as usize + }, + 24usize, + concat!( + "Offset of field: ", + stringify!(NK_storage_ProductionTest), + "::", + stringify!(SD_Card_Manufacturer_u8) + ) + ); +} +extern "C" { + pub fn NK_get_storage_production_info( + out: *mut NK_storage_ProductionTest, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Set debug level of messages written on stderr + /// @param state state=True - most messages, state=False - only errors level + pub fn NK_set_debug(state: bool); +} +extern "C" { + /// Set debug level of messages written on stderr + /// @param level (int) 0-lowest verbosity, 5-highest verbosity + pub fn NK_set_debug_level(level: ::std::os::raw::c_int); +} +extern "C" { + /// Get the major library version, e. g. the 3 in v3.2. + /// @return the major library version + pub fn NK_get_major_library_version() -> ::std::os::raw::c_uint; +} +extern "C" { + /// Get the minor library version, e. g. the 2 in v3.2. + /// @return the minor library version + pub fn NK_get_minor_library_version() -> ::std::os::raw::c_uint; +} +extern "C" { + /// Get the library version as a string. This is the output of + /// `git describe --always` at compile time, for example "v3.3" or + /// "v3.3-19-gaee920b". + /// The return value is a string literal and must not be freed. + /// @return the library version as a string + pub fn NK_get_library_version() -> *const ::std::os::raw::c_char; +} +extern "C" { + /// Connect to device of given model. Currently library can be connected only to one device at once. + /// @param device_model char 'S': Nitrokey Storage, 'P': Nitrokey Pro + /// @return 1 if connected, 0 if wrong model or cannot connect + pub fn NK_login(device_model: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + /// Connect to device of given model. Currently library can be connected only to one device at once. + /// @param device_model NK_device_model: NK_PRO: Nitrokey Pro, NK_STORAGE: Nitrokey Storage + /// @return 1 if connected, 0 if wrong model or cannot connect + pub fn NK_login_enum(device_model: NK_device_model) -> ::std::os::raw::c_int; +} +extern "C" { + /// Connect to first available device, starting checking from Pro 1st to Storage 2nd. + /// @return 1 if connected, 0 if wrong model or cannot connect + pub fn NK_login_auto() -> ::std::os::raw::c_int; +} +extern "C" { + /// Disconnect from the device. + /// @return command processing error code + pub fn NK_logout() -> ::std::os::raw::c_int; +} +extern "C" { + /// Query the model of the connected device. + /// Returns the model of the connected device or NK_DISCONNECTED. + /// + /// @return true if a device is connected and the out argument has been set + pub fn NK_get_device_model() -> NK_device_model; +} +extern "C" { + /// Return the debug status string. Debug purposes. + /// @return command processing error code + pub fn NK_status() -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Return the device's serial number string in hex. + /// @return string device's serial number in hex + pub fn NK_device_serial_number() -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get last command processing status. Useful for commands which returns the results of their own and could not return + /// an error code. + /// @return previous command processing error code + pub fn NK_get_last_command_status() -> u8; +} +extern "C" { + /// Lock device - cancel any user device unlocking. + /// @return command processing error code + pub fn NK_lock_device() -> ::std::os::raw::c_int; +} +extern "C" { + /// Authenticates the user on USER privilages with user_password and sets user's temporary password on device to user_temporary_password. + /// @param user_password char[25] current user password + /// @param user_temporary_password char[25] user temporary password to be set on device for further communication (authentication command) + /// @return command processing error code + pub fn NK_user_authenticate( + user_password: *const ::std::os::raw::c_char, + user_temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Authenticates the user on ADMIN privilages with admin_password and sets user's temporary password on device to admin_temporary_password. + /// @param admin_password char[25] current administrator PIN + /// @param admin_temporary_password char[25] admin temporary password to be set on device for further communication (authentication command) + /// @return command processing error code + pub fn NK_first_authenticate( + admin_password: *const ::std::os::raw::c_char, + admin_temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Execute a factory reset. + /// @param admin_password char[20] current administrator PIN + /// @return command processing error code + pub fn NK_factory_reset(admin_password: *const ::std::os::raw::c_char) + -> ::std::os::raw::c_int; +} +extern "C" { + /// Generates AES key on the device + /// @param admin_password char[20] current administrator PIN + /// @return command processing error code + pub fn NK_build_aes_key(admin_password: *const ::std::os::raw::c_char) + -> ::std::os::raw::c_int; +} +extern "C" { + /// Unlock user PIN locked after 3 incorrect codes tries. + /// @param admin_password char[20] current administrator PIN + /// @return command processing error code + pub fn NK_unlock_user_password( + admin_password: *const ::std::os::raw::c_char, + new_user_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Write general config to the device + /// @param numlock set value in range [0-1] to send HOTP code from slot 'numlock' after double pressing numlock + /// or outside the range to disable this function + /// @param capslock similar to numlock but with capslock + /// @param scrolllock similar to numlock but with scrolllock + /// @param enable_user_password set True to enable OTP PIN protection (require PIN each OTP code request) + /// @param delete_user_password (unused) + /// @param admin_temporary_password current admin temporary password + /// @return command processing error code + pub fn NK_write_config( + numlock: u8, + capslock: u8, + scrolllock: u8, + enable_user_password: bool, + delete_user_password: bool, + admin_temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and is enabled PIN protected OTP + /// @see NK_write_config + /// @return uint8_t general_config[5]: + /// uint8_t numlock; + /// uint8_t capslock; + /// uint8_t scrolllock; + /// uint8_t enable_user_password; + /// uint8_t delete_user_password; + pub fn NK_read_config() -> *mut u8; +} +extern "C" { + /// Get name of given TOTP slot + /// @param slot_number TOTP slot number, slot_number<15 + /// @return char[20] the name of the slot + pub fn NK_get_totp_slot_name(slot_number: u8) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// @param slot_number HOTP slot number, slot_number<3 + /// @return char[20] the name of the slot + pub fn NK_get_hotp_slot_name(slot_number: u8) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Erase HOTP slot data from the device + /// @param slot_number HOTP slot number, slot_number<3 + /// @param temporary_password admin temporary password + /// @return command processing error code + pub fn NK_erase_hotp_slot( + slot_number: u8, + temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Erase TOTP slot data from the device + /// @param slot_number TOTP slot number, slot_number<15 + /// @param temporary_password admin temporary password + /// @return command processing error code + pub fn NK_erase_totp_slot( + slot_number: u8, + temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Write HOTP slot data to the device + /// @param slot_number HOTP slot number, slot_number<3, 0-numbered + /// @param slot_name char[15] desired slot name. C string (requires ending '\0'; 16 bytes). + /// @param secret char[40] 160-bit or 320-bit (currently Pro v0.8 only) secret as a hex string. C string (requires ending '\0'; 41 bytes). + /// See NitrokeyManager::is_320_OTP_secret_supported. + /// @param hotp_counter uint32_t starting value of HOTP counter + /// @param use_8_digits should returned codes be 6 (false) or 8 digits (true) + /// @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock + /// @param use_tokenID @see token_ID + /// @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section + /// @param temporary_password char[25] admin temporary password + /// @return command processing error code + pub fn NK_write_hotp_slot( + slot_number: u8, + slot_name: *const ::std::os::raw::c_char, + secret: *const ::std::os::raw::c_char, + hotp_counter: u64, + use_8_digits: bool, + use_enter: bool, + use_tokenID: bool, + token_ID: *const ::std::os::raw::c_char, + temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Write TOTP slot data to the device + /// @param slot_number TOTP slot number, slot_number<15, 0-numbered + /// @param slot_name char[15] desired slot name. C string (requires ending '\0'; 16 bytes). + /// @param secret char[40] 160-bit or 320-bit (currently Pro v0.8 only) secret as a hex string. C string (requires ending '\0'; 41 bytes). + /// See NitrokeyManager::is_320_OTP_secret_supported. + /// @param time_window uint16_t time window for this TOTP + /// @param use_8_digits should returned codes be 6 (false) or 8 digits (true) + /// @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock + /// @param use_tokenID @see token_ID + /// @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section + /// @param temporary_password char[20] admin temporary password + /// @return command processing error code + pub fn NK_write_totp_slot( + slot_number: u8, + slot_name: *const ::std::os::raw::c_char, + secret: *const ::std::os::raw::c_char, + time_window: u16, + use_8_digits: bool, + use_enter: bool, + use_tokenID: bool, + token_ID: *const ::std::os::raw::c_char, + temporary_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Get HOTP code from the device + /// @param slot_number HOTP slot number, slot_number<3 + /// @return HOTP code + pub fn NK_get_hotp_code(slot_number: u8) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get HOTP code from the device (PIN protected) + /// @param slot_number HOTP slot number, slot_number<3 + /// @param user_temporary_password char[25] user temporary password if PIN protected OTP codes are enabled, + /// otherwise should be set to empty string - '' + /// @return HOTP code + pub fn NK_get_hotp_code_PIN( + slot_number: u8, + user_temporary_password: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get TOTP code from the device + /// @param slot_number TOTP slot number, slot_number<15 + /// @param challenge TOTP challenge -- unused + /// @param last_totp_time last time -- unused + /// @param last_interval last interval --unused + /// @return TOTP code + pub fn NK_get_totp_code( + slot_number: u8, + challenge: u64, + last_totp_time: u64, + last_interval: u8, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get TOTP code from the device (PIN protected) + /// @param slot_number TOTP slot number, slot_number<15 + /// @param challenge TOTP challenge -- unused + /// @param last_totp_time last time -- unused + /// @param last_interval last interval -- unused + /// @param user_temporary_password char[25] user temporary password if PIN protected OTP codes are enabled, + /// otherwise should be set to empty string - '' + /// @return TOTP code + pub fn NK_get_totp_code_PIN( + slot_number: u8, + challenge: u64, + last_totp_time: u64, + last_interval: u8, + user_temporary_password: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Set time on the device (for TOTP requests) + /// @param time seconds in unix epoch (from 01.01.1970) + /// @return command processing error code + pub fn NK_totp_set_time(time: u64) -> ::std::os::raw::c_int; +} +extern "C" { + /// Set the device time used for TOTP to the given time. Contrary to + /// {@code set_time(uint64_t)}, this command fails if {@code old_time} + /// > {@code time} or if {@code old_time} is zero (where {@code + /// old_time} is the current time on the device). + /// + /// @param time new device time as Unix timestamp (seconds since + /// 1970-01-01) + /// @return command processing error code + pub fn NK_totp_set_time_soft(time: u64) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn NK_totp_get_time() -> ::std::os::raw::c_int; +} +extern "C" { + /// Change administrator PIN + /// @param current_PIN char[25] current PIN + /// @param new_PIN char[25] new PIN + /// @return command processing error code + pub fn NK_change_admin_PIN( + current_PIN: *const ::std::os::raw::c_char, + new_PIN: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Change user PIN + /// @param current_PIN char[25] current PIN + /// @param new_PIN char[25] new PIN + /// @return command processing error code + pub fn NK_change_user_PIN( + current_PIN: *const ::std::os::raw::c_char, + new_PIN: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Get retry count of user PIN + /// @return user PIN retry count + pub fn NK_get_user_retry_count() -> u8; +} +extern "C" { + /// Get retry count of admin PIN + /// @return admin PIN retry count + pub fn NK_get_admin_retry_count() -> u8; +} +extern "C" { + /// Enable password safe access + /// @param user_pin char[30] current user PIN + /// @return command processing error code + pub fn NK_enable_password_safe( + user_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Get password safe slots' status + /// @return uint8_t[16] slot statuses - each byte represents one slot with 0 (not programmed) and 1 (programmed) + pub fn NK_get_password_safe_slot_status() -> *mut u8; +} +extern "C" { + /// Get password safe slot name + /// @param slot_number password safe slot number, slot_number<16 + /// @return slot name + pub fn NK_get_password_safe_slot_name(slot_number: u8) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get password safe slot login + /// @param slot_number password safe slot number, slot_number<16 + /// @return login from the PWS slot + pub fn NK_get_password_safe_slot_login(slot_number: u8) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get the password safe slot password + /// @param slot_number password safe slot number, slot_number<16 + /// @return password from the PWS slot + pub fn NK_get_password_safe_slot_password(slot_number: u8) -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Write password safe data to the slot + /// @param slot_number password safe slot number, slot_number<16 + /// @param slot_name char[11] name of the slot + /// @param slot_login char[32] login string + /// @param slot_password char[20] password string + /// @return command processing error code + pub fn NK_write_password_safe_slot( + slot_number: u8, + slot_name: *const ::std::os::raw::c_char, + slot_login: *const ::std::os::raw::c_char, + slot_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Erase the password safe slot from the device + /// @param slot_number password safe slot number, slot_number<16 + /// @return command processing error code + pub fn NK_erase_password_safe_slot(slot_number: u8) -> ::std::os::raw::c_int; +} +extern "C" { + /// Check whether AES is supported by the device + /// @return 0 for no and 1 for yes + pub fn NK_is_AES_supported( + user_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +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; +} +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; +} +extern "C" { + /// Function to determine unencrypted volume PIN type + /// @param minor_firmware_version + /// @return Returns 1, if set unencrypted volume ro/rw pin type is User, 0 otherwise. + pub fn NK_set_unencrypted_volume_rorw_pin_type_user() -> ::std::os::raw::c_int; +} +extern "C" { + /// This command is typically run to initiate + /// communication with the device (altough not required). + /// It sets time on device and returns its current status + /// - a combination of set_time and get_status_storage commands + /// Storage only + /// @param seconds_from_epoch date and time expressed in seconds + pub fn NK_send_startup(seconds_from_epoch: u64) -> ::std::os::raw::c_int; +} +extern "C" { + /// Unlock encrypted volume. + /// Storage only + /// @param user_pin user pin 20 characters + /// @return command processing error code + pub fn NK_unlock_encrypted_volume( + user_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Locks encrypted volume + /// @return command processing error code + pub fn NK_lock_encrypted_volume() -> ::std::os::raw::c_int; +} +extern "C" { + /// Unlock hidden volume and lock encrypted volume. + /// Requires encrypted volume to be unlocked. + /// Storage only + /// @param hidden_volume_password 20 characters + /// @return command processing error code + pub fn NK_unlock_hidden_volume( + hidden_volume_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Locks hidden volume + /// @return command processing error code + pub fn NK_lock_hidden_volume() -> ::std::os::raw::c_int; +} +extern "C" { + /// Create hidden volume. + /// Requires encrypted volume to be unlocked. + /// Storage only + /// @param slot_nr slot number in range 0-3 + /// @param start_percent volume begin expressed in percent of total available storage, int in range 0-99 + /// @param end_percent volume end expressed in percent of total available storage, int in range 1-100 + /// @param hidden_volume_password 20 characters + /// @return command processing error code + pub fn NK_create_hidden_volume( + slot_nr: u8, + start_percent: u8, + end_percent: u8, + hidden_volume_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Make unencrypted volume read-only. + /// Device hides unencrypted volume for a second therefore make sure + /// buffers are flushed before running. + /// Does nothing if firmware version is not matched + /// Firmware range: Storage v0.50, v0.48 and below + /// Storage only + /// @param user_pin 20 characters User PIN + /// @return command processing error code + pub fn NK_set_unencrypted_read_only( + user_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Make unencrypted volume read-write. + /// Device hides unencrypted volume for a second therefore make sure + /// buffers are flushed before running. + /// Does nothing if firmware version is not matched + /// Firmware range: Storage v0.50, v0.48 and below + /// Storage only + /// @param user_pin 20 characters User PIN + /// @return command processing error code + pub fn NK_set_unencrypted_read_write( + user_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Make unencrypted volume read-only. + /// Device hides unencrypted volume for a second therefore make sure + /// buffers are flushed before running. + /// Does nothing if firmware version is not matched + /// Firmware range: Storage v0.49, v0.51+ + /// Storage only + /// @param admin_pin 20 characters Admin PIN + /// @return command processing error code + pub fn NK_set_unencrypted_read_only_admin( + admin_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Make unencrypted volume read-write. + /// Device hides unencrypted volume for a second therefore make sure + /// buffers are flushed before running. + /// Does nothing if firmware version is not matched + /// Firmware range: Storage v0.49, v0.51+ + /// Storage only + /// @param admin_pin 20 characters Admin PIN + /// @return command processing error code + pub fn NK_set_unencrypted_read_write_admin( + admin_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Make encrypted volume read-only. + /// Device hides encrypted volume for a second therefore make sure + /// buffers are flushed before running. + /// Firmware range: v0.49 only, future (see firmware release notes) + /// Storage only + /// @param admin_pin 20 characters + /// @return command processing error code + pub fn NK_set_encrypted_read_only( + admin_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Make encrypted volume read-write. + /// Device hides encrypted volume for a second therefore make sure + /// buffers are flushed before running. + /// Firmware range: v0.49 only, future (see firmware release notes) + /// Storage only + /// @param admin_pin 20 characters + /// @return command processing error code + pub fn NK_set_encrypted_read_write( + admin_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Exports device's firmware to unencrypted volume. + /// Storage only + /// @param admin_pin 20 characters + /// @return command processing error code + pub fn NK_export_firmware(admin_pin: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + /// Clear new SD card notification. It is set after factory reset. + /// Storage only + /// @param admin_pin 20 characters + /// @return command processing error code + pub fn NK_clear_new_sd_card_warning( + admin_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Fill SD card with random data. + /// Should be done on first stick initialization after creating keys. + /// Storage only + /// @param admin_pin 20 characters + /// @return command processing error code + pub fn NK_fill_SD_card_with_random_data( + admin_pin: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Change update password. + /// Update password is used for entering update mode, where firmware + /// could be uploaded using dfu-programmer or other means. + /// Storage only + /// @param current_update_password 20 characters + /// @param new_update_password 20 characters + /// @return command processing error code + pub fn NK_change_update_password( + current_update_password: *const ::std::os::raw::c_char, + new_update_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Enter update mode. Needs update password. + /// When device is in update mode it no longer accepts any HID commands until + /// firmware is launched (regardless of being updated or not). + /// Smartcard (through CCID interface) and its all volumes are not visible as well. + /// Its VID and PID are changed to factory-default (03eb:2ff1 Atmel Corp.) + /// to be detected by flashing software. Result of this command can be reversed + /// by using 'launch' command. + /// For dfu-programmer it would be: 'dfu-programmer at32uc3a3256s launch'. + /// Storage only + /// @param update_password 20 characters + /// @return command processing error code + pub fn NK_enable_firmware_update( + update_password: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + /// Get Storage stick status as string. + /// Storage only + /// @return string with devices attributes + pub fn NK_get_status_storage_as_string() -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get the Storage stick status 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 storage status + /// @return command processing error code + pub fn NK_get_status_storage(out: *mut NK_storage_status) -> ::std::os::raw::c_int; +} +extern "C" { + /// Get SD card usage attributes as string. + /// Usable during hidden volumes creation. + /// Storage only + /// @return string with SD card usage attributes + pub fn NK_get_SD_usage_data_as_string() -> *mut ::std::os::raw::c_char; +} +extern "C" { + /// Get progress value of current long operation. + /// Storage only + /// @return int in range 0-100 or -1 if device is not busy + pub fn NK_get_progress_bar_value() -> ::std::os::raw::c_int; +} +extern "C" { + /// Returns a list of connected devices' id's, delimited by ';' character. Empty string is returned on no device found. + /// Each ID could consist of: + /// 1. SC_id:SD_id_p_path (about 40 bytes) + /// 2. path (about 10 bytes) + /// where 'path' is USB path (bus:num), 'SC_id' is smartcard ID, 'SD_id' is storage card ID and + /// '_p_' and ':' are field delimiters. + /// Case 2 (USB path only) is used, when the device cannot be asked about its status data (e.g. during a long operation, + /// like clearing SD card. + /// Internally connects to all available devices and creates a map between ids and connection objects. + /// Side effects: changes active device to last detected Storage device. + /// Storage only + /// @example Example of returned data: '00005d19:dacc2cb4_p_0001:0010:02;000037c7:4cf12445_p_0001:000f:02;0001:000c:02' + /// @return string delimited id's of connected devices + pub fn NK_list_devices_by_cpuID() -> *mut ::std::os::raw::c_char; +} +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. + /// Storage only + /// @param id Target device ID (example: '00005d19:dacc2cb4_p_0001:0010:02') + /// @return 1 on successful connection, 0 otherwise + pub fn NK_connect_with_ID(id: *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; +} diff --git a/nitrokey-sys/src/lib.rs b/nitrokey-sys/src/lib.rs new file mode 100644 index 0000000..2740cc9 --- /dev/null +++ b/nitrokey-sys/src/lib.rs @@ -0,0 +1,33 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +mod ffi; + +pub use ffi::*; + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + + #[test] + fn login_auto() { + unsafe { + // logout required due to https://github.com/Nitrokey/libnitrokey/pull/115 + NK_logout(); + assert_eq!(0, NK_login_auto()); + } + } + + #[test] + fn login() { + unsafe { + // Unconnected + assert_eq!(0, NK_login(CString::new("S").unwrap().as_ptr())); + assert_eq!(0, NK_login(CString::new("P").unwrap().as_ptr())); + // Unsupported model + assert_eq!(0, NK_login(CString::new("T").unwrap().as_ptr())); + } + } +} diff --git a/nitrokey/.gitignore b/nitrokey/.gitignore new file mode 100644 index 0000000..4cdf3b3 --- /dev/null +++ b/nitrokey/.gitignore @@ -0,0 +1,6 @@ + +/target +/nitrokey-sys/target +**/*.rs.bk +Cargo.lock +*.swp diff --git a/nitrokey/CHANGELOG.md b/nitrokey/CHANGELOG.md new file mode 100644 index 0000000..a60d6a7 --- /dev/null +++ b/nitrokey/CHANGELOG.md @@ -0,0 +1,22 @@ +# v0.2.1 (2018-12-10) + +- Re-export `device::{StorageStatus, VolumeStatus}` in `lib.rs`. + +# v0.2.0 (2018-12-10) + +- Update to libnitrokey v3.4.1. +- Major refactoring of the existing code structure. +- Add support for most of the Nitrokey Pro features and some of the Nitrokey + Storage features. See the `TODO.md` file for more details about the missing + functionality. + +# v0.1.1 (2018-05-21) + +- Update the `nitrokey-sys` dependency to version 3.3.0. Now `libnitrokey` + is built from source and `bindgen` is no longer a build dependency. +- Add `get_minor_firmware_version` to `Device`. +- Use `NK_login_enum` instead of `NK_login` in `Device::connect`. + +# v0.1.0 (2018-05-19) + +- Initial release diff --git a/nitrokey/Cargo.toml b/nitrokey/Cargo.toml new file mode 100644 index 0000000..dad751b --- /dev/null +++ b/nitrokey/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "nitrokey" +version = "0.2.1" +authors = ["Robin Krahl <robin.krahl@ireas.org>"] +homepage = "https://code.ireas.org/nitrokey-rs/" +repository = "https://git.ireas.org/nitrokey-rs/" +documentation = "https://docs.rs/nitrokey" +description = "Bindings to libnitrokey for communication with Nitrokey devices" +keywords = ["nitrokey", "otp"] +categories = ["api-bindings"] +readme = "README.md" +license = "MIT" + +[features] +test-no-device = [] +test-pro = [] +test-storage = [] + +[dependencies] +libc = "0.2" +nitrokey-sys = "3.4.1" +rand = "0.4" diff --git a/nitrokey/LICENSE b/nitrokey/LICENSE new file mode 100644 index 0000000..1a3601d --- /dev/null +++ b/nitrokey/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Robin Krahl <robin.krahl@ireas.org> + +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/nitrokey/README.md b/nitrokey/README.md new file mode 100644 index 0000000..6039943 --- /dev/null +++ b/nitrokey/README.md @@ -0,0 +1,84 @@ +# nitrokey-rs + +A libnitrokey wrapper for Rust providing access to Nitrokey devices. + +[Documentation][] + +```toml +[dependencies] +nitrokey = "0.2.1" +``` + +## Compatibility + +The required [`libnitrokey`][] version is built from source. The host system +must provide `libhidapi-libusb0` in the default library search path. + +Currently, this crate provides access to the common features of the Nitrokey +Pro and the Nitrokey Storage: general configuration, OTP generation and the +password safe. Basic support for the secure storage on the Nitrokey Storage is +available but still under development. + +### Unsupported Functions + +The following functions provided by `libnitrokey` are deliberately not +supported by `nitrokey-rs`: + +- `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`. + +## Tests + +This crate has three test suites that can be selected using features. One test +suite (feature `test-no-device`) assumes that no Nitrokey device is connected. +The two other test suites require a Nitrokey Pro (feature `test-pro`) or a +Nitrokey Storage (feature `test-storage`) to be connected. + +Use the `--features` option for Cargo to select one of the test suites. You +cannot select more than one of the test suites at the same time. Note that the +test suites that require a Nitrokey device assume that the device’s passwords +are the factory defaults (admin password `12345678` and user password +`123456`). Running the test suite with a device with different passwords might +lock your device! Also note that the test suite might delete or overwrite data +on all connected devices. + +As the tests currently are not synchronized, you have to make sure that they +are not executed in parallel. To do so, pass the option `--test-threads 1` to +the test executable. + +In conclusion, you can use these commands to run the test suites: + +``` +$ cargo test --features test-no-device -- --test-threads 1 +$ cargo test --features test-pro -- --test-threads 1 +$ cargo test --features test-storage -- --test-threads 1 +``` + +The `totp_no_pin` and `totp_pin` tests can occasionally fail due to bad timing. + +## Acknowledgments + +Thanks to Nitrokey UG for providing a Nitrokey Storage to support the +development of this crate. + +## Contact + +For bug reports, patches, feature requests or other messages, please send a +mail to [nitrokey-rs-dev@ireas.org][]. + +## License + +This project is licensed under the [MIT License][]. `libnitrokey` is licensed +under the [LGPL-3.0][]. + +[Documentation]: https://docs.rs/nitrokey +[`libnitrokey`]: https://github.com/nitrokey/libnitrokey +[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 diff --git a/nitrokey/TODO.md b/nitrokey/TODO.md new file mode 100644 index 0000000..6086ad8 --- /dev/null +++ b/nitrokey/TODO.md @@ -0,0 +1,46 @@ +- Add support for the currently unsupported commands: + - `NK_set_unencrypted_volume_rorw_pin_type_user` + - `NK_factory_reset` + - `NK_build_aes_key` + - `NK_is_AES_supported` + - `NK_send_startup` + - `NK_unlock_hidden_volume` + - `NK_lock_hidden_volume` + - `NK_create_hidden_volume` + - `NK_set_unencrypted_read_only` + - `NK_set_unencrypted_read_only_admin` + - `NK_set_unencrypted_read_write` + - `NK_set_unencrypted_read_write_admin` + - `NK_set_encrypted_read_only` + - `NK_set_encrypted_read_write` + - `NK_enable_firmware_update` + - `NK_export_firmware` + - `NK_clear_new_sd_card_warning` + - `NK_fill_SD_card_with_random_data` + - `NK_change_update_password` + - `NK_get_SD_usage_data_as_string` + - `NK_get_progress_bar_value` + - `NK_list_devices_by_cpuID` + - `NK_connect_with_ID` + - `NK_get_device_model` + - `NK_get_library_version` + - `NK_get_major_library_version` + - `NK_get_minor_libray_version` + - `NK_get_storage_production_info` + - `NK_totp_set_time_soft` + - `NK_wink` +- Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. +- Clear passwords from memory. +- Find a nicer syntax for the `write_config` test. +- Prevent construction of internal types. +- More specific error checking in the tests. +- Differentiate empty strings and errors (see `result_from_string`). +- Check integer conversions. +- Consider implementing `Into<CommandError>` for `(Device, CommandError)` +- Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware + issue 65][]). +- Disable creation of multiple password safes at the same time. +- Check timing in Storage tests. +- Consider restructuring `device::StorageStatus`. + +[nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 diff --git a/nitrokey/src/auth.rs b/nitrokey/src/auth.rs new file mode 100644 index 0000000..0918222 --- /dev/null +++ b/nitrokey/src/auth.rs @@ -0,0 +1,412 @@ +use config::{Config, RawConfig}; +use device::{Device, DeviceWrapper, Pro, Storage}; +use nitrokey_sys; +use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData}; +use std::ops::Deref; +use std::os::raw::c_int; +use util::{generate_password, get_command_result, get_cstring, result_from_string, CommandError}; + +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 { + /// 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. + /// + /// This method generates a random temporary password that is used for all operations that + /// require user access. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided user password contains a null byte + /// - [`RngError`][] if the generation of the temporary password failed + /// - [`WrongPassword`][] if the provided user password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, DeviceWrapper, User}; + /// # use nitrokey::CommandError; + /// + /// fn perform_user_task(device: &User<DeviceWrapper>) {} + /// fn perform_other_task(device: &DeviceWrapper) {} + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let device = match device.authenticate_user("123456") { + /// Ok(user) => { + /// perform_user_task(&user); + /// user.device() + /// }, + /// Err((device, err)) => { + /// println!("Could not authenticate as user: {}", err); + /// device + /// }, + /// }; + /// perform_other_task(&device); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.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, CommandError)> + where + Self: Device + Sized; + + /// Performs admin authentication. This method consumes the device. If successful, an + /// authenticated device is returned. Otherwise, the current unauthenticated device and the + /// error are returned. + /// + /// This method generates a random temporary password that is used for all operations that + /// require admin access. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided admin password contains a null byte + /// - [`RngError`][] if the generation of the temporary password failed + /// - [`WrongPassword`][] if the provided admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, Admin, DeviceWrapper}; + /// # use nitrokey::CommandError; + /// + /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {} + /// fn perform_other_task(device: &DeviceWrapper) {} + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let device = match device.authenticate_admin("123456") { + /// Ok(admin) => { + /// perform_admin_task(&admin); + /// admin.device() + /// }, + /// Err((device, err)) => { + /// println!("Could not authenticate as admin: {}", err); + /// device + /// }, + /// }; + /// perform_other_task(&device); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.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, CommandError)> + where + Self: Device + Sized; +} + +trait AuthenticatedDevice<T> { + fn new(device: T, temp_password: Vec<u8>) -> Self; +} + +/// A Nitrokey device with user authentication. +/// +/// To obtain an instance of this struct, use the [`authenticate_user`][] method from the +/// [`Authenticate`][] trait. To get back to an unauthenticated device, use the [`device`][] +/// method. +/// +/// [`Authenticate`]: trait.Authenticate.html +/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`device`]: #method.device +#[derive(Debug)] +pub struct User<T: Device> { + device: T, + temp_password: Vec<u8>, +} + +/// A Nitrokey device with admin authentication. +/// +/// To obtain an instance of this struct, use the [`authenticate_admin`][] method from the +/// [`Authenticate`][] trait. To get back to an unauthenticated device, use the [`device`][] +/// method. +/// +/// [`Authenticate`]: trait.Authenticate.html +/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`device`]: #method.device +#[derive(Debug)] +pub struct Admin<T: Device> { + device: T, + temp_password: Vec<u8>, +} + +fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, CommandError)> +where + D: Device, + A: AuthenticatedDevice<D>, + T: Fn(*const i8, *const i8) -> c_int, +{ + let temp_password = match generate_password(TEMPORARY_PASSWORD_LENGTH) { + Ok(pw) => pw, + Err(_) => return Err((device, CommandError::RngError)), + }; + let password = match get_cstring(password) { + Ok(password) => password, + Err(err) => return Err((device, err)), + }; + let password_ptr = password.as_ptr(); + let temp_password_ptr = temp_password.as_ptr() as *const i8; + return match callback(password_ptr, temp_password_ptr) { + 0 => Ok(A::new(device, temp_password)), + rv => Err((device, CommandError::from(rv))), + }; +} + +fn authenticate_user_wrapper<T, C>( + device: T, + constructor: C, + password: &str, +) -> Result<User<DeviceWrapper>, (DeviceWrapper, CommandError)> +where + T: Device, + C: Fn(T) -> DeviceWrapper, +{ + let result = device.authenticate_user(password); + match result { + Ok(user) => Ok(User::new(constructor(user.device), user.temp_password)), + Err((device, err)) => Err((constructor(device), err)), + } +} + +fn authenticate_admin_wrapper<T, C>( + device: T, + constructor: C, + password: &str, +) -> Result<Admin<DeviceWrapper>, (DeviceWrapper, CommandError)> +where + T: Device, + C: Fn(T) -> DeviceWrapper, +{ + let result = device.authenticate_admin(password); + match result { + Ok(user) => Ok(Admin::new(constructor(user.device), user.temp_password)), + Err((device, err)) => Err((constructor(device), err)), + } +} + +impl<T: Device> User<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. + pub fn device(self) -> T { + self.device + } +} + +impl<T: Device> Deref for User<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.device + } +} + +impl<T: Device> GenerateOtp for User<T> { + fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> { + unsafe { + let temp_password_ptr = self.temp_password.as_ptr() as *const i8; + return result_from_string(nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password_ptr)); + } + } + + fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> { + unsafe { + let temp_password_ptr = self.temp_password.as_ptr() as *const i8; + return result_from_string(nitrokey_sys::NK_get_totp_code_PIN( + slot, + 0, + 0, + 0, + temp_password_ptr, + )); + } + } +} + +impl<T: Device> AuthenticatedDevice<T> for User<T> { + fn new(device: T, temp_password: Vec<u8>) -> Self { + User { + device, + temp_password, + } + } +} + +impl<T: Device> Deref for Admin<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.device + } +} + +impl<T: Device> Admin<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. + pub fn device(self) -> T { + self.device + } + + /// Writes the given configuration to the Nitrokey device. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if the provided numlock, capslock or scrolllock slot is larger than two + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, Config}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let config = Config::new(None, None, None, false); + /// match device.authenticate_admin("12345678") { + /// Ok(admin) => { + /// admin.write_config(config); + /// () + /// }, + /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + pub fn write_config(&self, config: Config) -> Result<(), CommandError> { + let raw_config = RawConfig::try_from(config)?; + unsafe { + get_command_result(nitrokey_sys::NK_write_config( + raw_config.numlock, + raw_config.capslock, + raw_config.scrollock, + raw_config.user_password, + false, + self.temp_password.as_ptr() as *const i8, + )) + } + } + + fn write_otp_slot<C>(&self, data: OtpSlotData, callback: C) -> Result<(), CommandError> + where + C: Fn(RawOtpSlotData, *const i8) -> c_int, + { + let raw_data = RawOtpSlotData::new(data)?; + let temp_password_ptr = self.temp_password.as_ptr() as *const i8; + get_command_result(callback(raw_data, temp_password_ptr)) + } +} + +impl<T: Device> ConfigureOtp for Admin<T> { + fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError> { + self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { + nitrokey_sys::NK_write_hotp_slot( + raw_data.number, + raw_data.name.as_ptr(), + raw_data.secret.as_ptr(), + counter, + raw_data.mode == OtpMode::EightDigits, + raw_data.use_enter, + raw_data.use_token_id, + raw_data.token_id.as_ptr(), + temp_password_ptr, + ) + }) + } + + fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError> { + self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { + nitrokey_sys::NK_write_totp_slot( + raw_data.number, + raw_data.name.as_ptr(), + raw_data.secret.as_ptr(), + time_window, + raw_data.mode == OtpMode::EightDigits, + raw_data.use_enter, + raw_data.use_token_id, + raw_data.token_id.as_ptr(), + temp_password_ptr, + ) + }) + } + + fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError> { + let temp_password_ptr = self.temp_password.as_ptr() as *const i8; + unsafe { get_command_result(nitrokey_sys::NK_erase_hotp_slot(slot, temp_password_ptr)) } + } + + fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError> { + let temp_password_ptr = self.temp_password.as_ptr() as *const i8; + unsafe { get_command_result(nitrokey_sys::NK_erase_totp_slot(slot, temp_password_ptr)) } + } +} + +impl<T: Device> AuthenticatedDevice<T> for Admin<T> { + fn new(device: T, temp_password: Vec<u8>) -> Self { + Admin { + device, + temp_password, + } + } +} + +impl Authenticate for DeviceWrapper { + fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> { + match self { + DeviceWrapper::Storage(storage) => { + authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) + } + DeviceWrapper::Pro(pro) => authenticate_user_wrapper(pro, DeviceWrapper::Pro, password), + } + } + + fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> { + match self { + DeviceWrapper::Storage(storage) => { + authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) + } + DeviceWrapper::Pro(pro) => { + authenticate_admin_wrapper(pro, DeviceWrapper::Pro, password) + } + } + } +} + +impl Authenticate for Pro { + fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> { + 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, CommandError)> { + 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, CommandError)> { + 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, CommandError)> { + authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { + nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) + }) + } +} diff --git a/nitrokey/src/config.rs b/nitrokey/src/config.rs new file mode 100644 index 0000000..33bf256 --- /dev/null +++ b/nitrokey/src/config.rs @@ -0,0 +1,99 @@ +use util::CommandError; + +/// The configuration for a Nitrokey. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Config { + /// If set, the stick will generate a code from the HOTP slot with the given number if numlock + /// is pressed. The slot number must be 0, 1 or 2. + pub numlock: Option<u8>, + /// If set, the stick will generate a code from the HOTP slot with the given number if capslock + /// is pressed. The slot number must be 0, 1 or 2. + pub capslock: Option<u8>, + /// If set, the stick will generate a code from the HOTP slot with the given number if + /// scrollock is pressed. The slot number must be 0, 1 or 2. + pub scrollock: Option<u8>, + /// If set, OTP generation using [`get_hotp_code`][] or [`get_totp_code`][] requires user + /// authentication. Otherwise, OTPs can be generated without authentication. + /// + /// [`get_hotp_code`]: trait.ProvideOtp.html#method.get_hotp_code + /// [`get_totp_code`]: trait.ProvideOtp.html#method.get_totp_code + pub user_password: bool, +} + +#[derive(Debug)] +pub struct RawConfig { + pub numlock: u8, + pub capslock: u8, + pub scrollock: u8, + pub user_password: bool, +} + +fn config_otp_slot_to_option(value: u8) -> Option<u8> { + if value < 3 { + return Some(value); + } + None +} + +fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, CommandError> { + match value { + Some(value) => { + if value < 3 { + Ok(value) + } else { + Err(CommandError::InvalidSlot) + } + } + None => Ok(255), + } +} + +impl Config { + /// Constructs a new instance of this struct. + pub fn new( + numlock: Option<u8>, + capslock: Option<u8>, + scrollock: Option<u8>, + user_password: bool, + ) -> Config { + Config { + numlock, + capslock, + scrollock, + user_password, + } + } +} + +impl RawConfig { + pub fn try_from(config: Config) -> Result<RawConfig, CommandError> { + Ok(RawConfig { + numlock: option_to_config_otp_slot(config.numlock)?, + capslock: option_to_config_otp_slot(config.capslock)?, + scrollock: option_to_config_otp_slot(config.scrollock)?, + user_password: config.user_password, + }) + } +} + +impl From<[u8; 5]> for RawConfig { + fn from(data: [u8; 5]) -> Self { + RawConfig { + numlock: data[0], + capslock: data[1], + scrollock: data[2], + user_password: data[3] != 0, + } + } +} + +impl Into<Config> for RawConfig { + fn into(self) -> Config { + Config { + numlock: config_otp_slot_to_option(self.numlock), + capslock: config_otp_slot_to_option(self.capslock), + scrollock: config_otp_slot_to_option(self.scrollock), + user_password: self.user_password, + } + } +} diff --git a/nitrokey/src/device.rs b/nitrokey/src/device.rs new file mode 100644 index 0000000..f135261 --- /dev/null +++ b/nitrokey/src/device.rs @@ -0,0 +1,749 @@ +use auth::Authenticate; +use config::{Config, RawConfig}; +use libc; +use nitrokey_sys; +use otp::GenerateOtp; +use pws::GetPasswordSafe; +use util::{get_command_result, get_cstring, get_last_error, result_from_string, CommandError}; + +/// Available Nitrokey models. +#[derive(Debug, PartialEq)] +enum Model { + /// The Nitrokey Storage. + Storage, + /// The Nitrokey Pro. + Pro, +} + +/// A wrapper for a Nitrokey device of unknown type. +/// +/// Use the function [`connect`][] 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. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, DeviceWrapper, User}; +/// # use nitrokey::CommandError; +/// +/// fn perform_user_task(device: &User<DeviceWrapper>) {} +/// fn perform_other_task(device: &DeviceWrapper) {} +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// println!("Could not authenticate as user: {}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// +/// Device-specific commands: +/// +/// ```no_run +/// use nitrokey::{DeviceWrapper, Storage}; +/// # use nitrokey::CommandError; +/// +/// fn perform_common_task(device: &DeviceWrapper) {} +/// fn perform_storage_task(device: &Storage) {} +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::connect()?; +/// perform_common_task(&device); +/// match device { +/// DeviceWrapper::Storage(storage) => perform_storage_task(&storage), +/// _ => (), +/// }; +/// # Ok(()) +/// # } +/// ``` +/// +/// [`connect`]: fn.connect.html +// TODO: add example for Storage-specific code +#[derive(Debug)] +pub enum DeviceWrapper { + /// A Nitrokey Storage device. + Storage(Storage), + /// A Nitrokey Pro device. + Pro(Pro), +} + +/// 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`][]. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, User, Pro}; +/// # use nitrokey::CommandError; +/// +/// fn perform_user_task(device: &User<Pro>) {} +/// fn perform_other_task(device: &Pro) {} +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::Pro::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// println!("Could not authenticate as user: {}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user +/// [`connect`]: fn.connect.html +/// [`Pro::connect`]: #method.connect +#[derive(Debug)] +pub struct Pro {} + +/// 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`][]. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, User, Storage}; +/// # use nitrokey::CommandError; +/// +/// fn perform_user_task(device: &User<Storage>) {} +/// fn perform_other_task(device: &Storage) {} +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::Storage::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// println!("Could not authenticate as user: {}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user +/// [`connect`]: fn.connect.html +/// [`Storage::connect`]: #method.connect +#[derive(Debug)] +pub struct Storage {} + +/// The status of a volume on a Nitrokey Storage device. +#[derive(Debug)] +pub struct VolumeStatus { + /// Indicates whether the volume is read-only. + pub read_only: bool, + /// Indicates whether the volume is active. + pub active: bool, +} + +/// The status of a Nitrokey Storage device. +#[derive(Debug)] +pub struct StorageStatus { + /// The status of the unencrypted volume. + pub unencrypted_volume: VolumeStatus, + /// The status of the encrypted volume. + pub encrypted_volume: VolumeStatus, + /// The status of the hidden volume. + pub hidden_volume: VolumeStatus, + /// 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, + /// Indicates whether the firmware is locked. + pub firmware_locked: bool, + /// The serial number of the SD card in the Storage stick. + pub serial_number_sd_card: u32, + /// The serial number of the smart card in the Storage stick. + pub serial_number_smart_card: u32, + /// The number of remaining login attempts for the user PIN. + pub user_retry_count: u8, + /// The number of remaining login attempts for the admin PIN. + pub admin_retry_count: u8, + /// Indicates whether a new SD card was found. + pub new_sd_card_found: bool, + /// Indicates whether the SD card is filled with random characters. + pub filled_with_random: bool, + /// Indicates whether the stick has been initialized by generating + /// the AES keys. + pub stick_initialized: bool, +} + +/// A Nitrokey device. +/// +/// 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 { + /// Returns the serial number of the Nitrokey device. The serial number is the string + /// representation of a hex number. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.get_serial_number() { + /// Ok(number) => println!("serial no: {}", number), + /// Err(err) => println!("Could not get serial number: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + fn get_serial_number(&self) -> Result<String, CommandError> { + unsafe { result_from_string(nitrokey_sys::NK_device_serial_number()) } + } + + /// Returns the number of remaining authentication attempts for the user. The total number of + /// available attempts is three. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let count = device.get_user_retry_count(); + /// println!("{} remaining authentication attempts (user)", count); + /// # Ok(()) + /// # } + /// ``` + fn get_user_retry_count(&self) -> u8 { + unsafe { nitrokey_sys::NK_get_user_retry_count() } + } + + /// Returns the number of remaining authentication attempts for the admin. The total number of + /// available attempts is three. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let count = device.get_admin_retry_count(); + /// println!("{} remaining authentication attempts (admin)", count); + /// # Ok(()) + /// # } + /// ``` + fn get_admin_retry_count(&self) -> u8 { + unsafe { nitrokey_sys::NK_get_admin_retry_count() } + } + + /// Returns the major part of the firmware version (should be zero). + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// println!( + /// "Firmware version: {}.{}", + /// device.get_major_firmware_version(), + /// device.get_minor_firmware_version(), + /// ); + /// # Ok(()) + /// # } + /// ``` + fn get_major_firmware_version(&self) -> i32 { + unsafe { nitrokey_sys::NK_get_major_firmware_version() } + } + + /// Returns the minor part of the firmware version (for example 8 for version 0.8). + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// println!( + /// "Firmware version: {}.{}", + /// device.get_major_firmware_version(), + /// device.get_minor_firmware_version(), + /// ); + /// # Ok(()) + /// # } + fn get_minor_firmware_version(&self) -> i32 { + unsafe { nitrokey_sys::NK_get_minor_firmware_version() } + } + + /// Returns the current configuration of the Nitrokey device. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let config = device.get_config()?; + /// println!("numlock binding: {:?}", config.numlock); + /// println!("capslock binding: {:?}", config.capslock); + /// println!("scrollock binding: {:?}", config.scrollock); + /// println!("require password for OTP: {:?}", config.user_password); + /// # Ok(()) + /// # } + /// ``` + fn get_config(&self) -> Result<Config, CommandError> { + unsafe { + let config_ptr = nitrokey_sys::NK_read_config(); + if config_ptr.is_null() { + return Err(get_last_error()); + } + let config_array_ptr = config_ptr as *const [u8; 5]; + let raw_config = RawConfig::from(*config_array_ptr); + libc::free(config_ptr as *mut libc::c_void); + return Ok(raw_config.into()); + } + } + + /// Changes the administrator PIN. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.change_admin_pin("12345678", "12345679") { + /// Ok(()) => println!("Updated admin PIN."), + /// Err(err) => println!("Failed to update admin PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), CommandError> { + let current_string = get_cstring(current)?; + let new_string = get_cstring(new)?; + unsafe { + get_command_result(nitrokey_sys::NK_change_admin_PIN( + current_string.as_ptr(), + new_string.as_ptr(), + )) + } + } + + /// Changes the user PIN. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current user password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.change_user_pin("123456", "123457") { + /// Ok(()) => println!("Updated admin PIN."), + /// Err(err) => println!("Failed to update admin PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + fn change_user_pin(&self, current: &str, new: &str) -> Result<(), CommandError> { + let current_string = get_cstring(current)?; + let new_string = get_cstring(new)?; + unsafe { + get_command_result(nitrokey_sys::NK_change_user_PIN( + current_string.as_ptr(), + new_string.as_ptr(), + )) + } + } + + /// Unlocks the user PIN after three failed login attempts and sets it to the given value. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.unlock_user_pin("12345678", "123456") { + /// Ok(()) => println!("Unlocked user PIN."), + /// Err(err) => println!("Failed to unlock user PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), CommandError> { + let admin_pin_string = get_cstring(admin_pin)?; + let user_pin_string = get_cstring(user_pin)?; + unsafe { + get_command_result(nitrokey_sys::NK_unlock_user_password( + admin_pin_string.as_ptr(), + user_pin_string.as_ptr(), + )) + } + } + + /// Locks the Nitrokey device. + /// + /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this + /// also disables the volumes if they have been enabled. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.lock() { + /// Ok(()) => println!("Locked the Nitrokey device."), + /// Err(err) => println!("Could not lock the Nitrokey device: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + fn lock(&self) -> Result<(), CommandError> { + unsafe { get_command_result(nitrokey_sys::NK_lock_device()) } + } +} + +/// Connects to a Nitrokey device. This method can be used to connect to any connected device, +/// both a Nitrokey Pro and a Nitrokey Storage. +/// +/// # Example +/// +/// ``` +/// use nitrokey::DeviceWrapper; +/// +/// fn do_something(device: DeviceWrapper) {} +/// +/// match nitrokey::connect() { +/// Ok(device) => do_something(device), +/// Err(err) => println!("Could not connect to a Nitrokey: {}", err), +/// } +/// ``` +pub fn connect() -> Result<DeviceWrapper, CommandError> { + unsafe { + match nitrokey_sys::NK_login_auto() { + 1 => match get_connected_device() { + Some(wrapper) => Ok(wrapper), + None => Err(CommandError::Unknown), + }, + _ => Err(CommandError::Unknown), + } + } +} + +fn get_connected_model() -> Option<Model> { + unsafe { + match nitrokey_sys::NK_get_device_model() { + nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), + nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), + _ => None, + } + } +} + +fn create_device_wrapper(model: Model) -> DeviceWrapper { + match model { + Model::Pro => DeviceWrapper::Pro(Pro {}), + Model::Storage => DeviceWrapper::Storage(Storage {}), + } +} + +fn get_connected_device() -> Option<DeviceWrapper> { + get_connected_model().map(create_device_wrapper) +} + +fn connect_model(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, + }; + unsafe { nitrokey_sys::NK_login_enum(model) == 1 } +} + +impl DeviceWrapper { + fn device(&self) -> &Device { + match *self { + DeviceWrapper::Storage(ref storage) => storage, + DeviceWrapper::Pro(ref pro) => pro, + } + } +} + +impl GenerateOtp for DeviceWrapper { + fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> { + self.device().get_hotp_slot_name(slot) + } + + fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> { + self.device().get_totp_slot_name(slot) + } + + fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> { + self.device().get_hotp_code(slot) + } + + fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> { + self.device().get_totp_code(slot) + } +} + +impl Device for DeviceWrapper {} + +impl Pro { + pub fn connect() -> Result<Pro, CommandError> { + // TODO: maybe Option instead of Result? + match connect_model(Model::Pro) { + true => Ok(Pro {}), + false => Err(CommandError::Unknown), + } + } +} + +impl Drop for Pro { + fn drop(&mut self) { + unsafe { + nitrokey_sys::NK_logout(); + } + } +} + +impl Device for Pro {} + +impl GenerateOtp for Pro {} + +impl Storage { + pub fn connect() -> Result<Storage, CommandError> { + // TODO: maybe Option instead of Result? + match connect_model(Model::Storage) { + true => Ok(Storage {}), + false => Err(CommandError::Unknown), + } + } + + /// Enables the encrypted storage volume. + /// + /// Once the encrypted volume is enabled, it is presented to the operating system as a block + /// device. The API does not provide any information on the name or path of this block device. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided user password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.enable_encrypted_volume("123456") { + /// Ok(()) => println!("Enabled the encrypted volume."), + /// Err(err) => println!("Could not enable the encrypted volume: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), CommandError> { + let user_pin = get_cstring(user_pin)?; + unsafe { get_command_result(nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())) } + } + + /// Disables the encrypted storage volume. + /// + /// Once the volume is disabled, it can be no longer accessed as a block device. If the + /// encrypted volume has not been enabled, this method still returns a success. + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::CommandError; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.enable_encrypted_volume("123456") { + /// Ok(()) => { + /// println!("Enabled the encrypted volume."); + /// use_volume(); + /// match device.disable_encrypted_volume() { + /// Ok(()) => println!("Disabled the encrypted volume."), + /// Err(err) => { + /// println!("Could not disable the encrypted volume: {}", err); + /// }, + /// }; + /// }, + /// Err(err) => println!("Could not enable the encrypted volume: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + pub fn disable_encrypted_volume(&self) -> Result<(), CommandError> { + unsafe { get_command_result(nitrokey_sys::NK_lock_encrypted_volume()) } + } + + + /// Returns the status of the connected storage device. + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::CommandError; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.get_status() { + /// Ok(status) => { + /// println!("SD card ID: {:#x}", status.serial_number_sd_card); + /// }, + /// Err(err) => println!("Could not get Storage status: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + pub fn get_status(&self) -> Result<StorageStatus, CommandError> { + let mut raw_status = nitrokey_sys::NK_storage_status { + unencrypted_volume_read_only: false, + unencrypted_volume_active: false, + encrypted_volume_read_only: false, + encrypted_volume_active: false, + hidden_volume_read_only: false, + hidden_volume_active: false, + firmware_version_major: 0, + firmware_version_minor: 0, + firmware_locked: false, + serial_number_sd_card: 0, + serial_number_smart_card: 0, + user_retry_count: 0, + admin_retry_count: 0, + new_sd_card_found: false, + filled_with_random: false, + stick_initialized: false, + }; + let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) }; + let result = get_command_result(raw_result); + result.and(Ok(StorageStatus::from(raw_status))) + } +} + +impl Drop for Storage { + fn drop(&mut self) { + unsafe { + nitrokey_sys::NK_logout(); + } + } +} + +impl Device for Storage {} + +impl GenerateOtp for Storage {} + +impl From<nitrokey_sys::NK_storage_status> for StorageStatus { + fn from(status: nitrokey_sys::NK_storage_status) -> Self { + StorageStatus { + unencrypted_volume: VolumeStatus { + read_only: status.unencrypted_volume_read_only, + active: status.unencrypted_volume_active, + }, + encrypted_volume: VolumeStatus { + read_only: status.encrypted_volume_read_only, + active: status.encrypted_volume_active, + }, + hidden_volume: VolumeStatus { + read_only: status.hidden_volume_read_only, + active: status.hidden_volume_active, + }, + firmware_version_major: status.firmware_version_major, + firmware_version_minor: status.firmware_version_minor, + firmware_locked: status.firmware_locked, + serial_number_sd_card: status.serial_number_sd_card, + serial_number_smart_card: status.serial_number_smart_card, + user_retry_count: status.user_retry_count, + admin_retry_count: status.admin_retry_count, + new_sd_card_found: status.new_sd_card_found, + filled_with_random: status.filled_with_random, + stick_initialized: status.stick_initialized, + } + } +} diff --git a/nitrokey/src/lib.rs b/nitrokey/src/lib.rs new file mode 100644 index 0000000..e70aa73 --- /dev/null +++ b/nitrokey/src/lib.rs @@ -0,0 +1,127 @@ +//! Provides access to a Nitrokey device using the native libnitrokey API. +//! +//! # Usage +//! +//! Operations on the Nitrokey require different authentication levels. Some operations can be +//! 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 +//! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices. You can also use +//! [`Pro::connect`][] or [`Storage::connect`][] 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. +//! +//! 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 +//! passwords – [`get_hotp_code`][] and [`get_totp_code`][]. Depending on the stick configuration, +//! these operations are available without authentication or with user authentication. +//! +//! # Examples +//! +//! Connect to any Nitrokey and print its serial number: +//! +//! ```no_run +//! use nitrokey::Device; +//! # use nitrokey::CommandError; +//! +//! # fn try_main() -> Result<(), CommandError> { +//! let device = nitrokey::connect()?; +//! println!("{}", device.get_serial_number()?); +//! # Ok(()) +//! # } +//! ``` +//! +//! Configure an HOTP slot: +//! +//! ```no_run +//! use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData}; +//! # use nitrokey::CommandError; +//! +//! # fn try_main() -> Result<(), (CommandError)> { +//! let device = nitrokey::connect()?; +//! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); +//! match device.authenticate_admin("12345678") { +//! Ok(admin) => { +//! match admin.write_hotp_slot(slot_data, 0) { +//! Ok(()) => println!("Successfully wrote slot."), +//! Err(err) => println!("Could not write slot: {}", err), +//! } +//! }, +//! Err((_, err)) => println!("Could not authenticate as admin: {}", err), +//! } +//! # Ok(()) +//! # } +//! ``` +//! +//! Generate an HOTP one-time password: +//! +//! ```no_run +//! use nitrokey::{Device, GenerateOtp}; +//! # use nitrokey::CommandError; +//! +//! # fn try_main() -> Result<(), (CommandError)> { +//! let device = nitrokey::connect()?; +//! match device.get_hotp_code(1) { +//! Ok(code) => println!("Generated HOTP code: {}", code), +//! Err(err) => println!("Could not generate HOTP code: {}", err), +//! } +//! # Ok(()) +//! # } +//! ``` +//! +//! [`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 +//! [`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 +//! [`Admin`]: struct.Admin.html +//! [`DeviceWrapper`]: enum.DeviceWrapper.html +//! [`User`]: struct.User.html + +extern crate libc; +extern crate nitrokey_sys; +extern crate rand; + +mod auth; +mod config; +mod device; +mod otp; +mod pws; +#[cfg(test)] +mod tests; +mod util; + +pub use auth::{Admin, Authenticate, User}; +pub use config::Config; +pub use device::{connect, Device, DeviceWrapper, Pro, Storage, StorageStatus, VolumeStatus}; +pub use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; +pub use pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; +pub use util::{CommandError, LogLevel}; + +/// 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`][]). +/// +/// If debug output is enabled, detailed information about the communication with the Nitrokey +/// device is printed to the standard output. +/// +/// [`set_log_level`]: fn.set_log_level.html +pub fn set_debug(state: bool) { + unsafe { + nitrokey_sys::NK_set_debug(state); + } +} + +/// Sets the log level for libnitrokey. All log messages are written to the standard error stream. +/// Setting the log level enables all log messages on the same or on a higher log level. +pub fn set_log_level(level: LogLevel) { + unsafe { + nitrokey_sys::NK_set_debug_level(level.into()); + } +} diff --git a/nitrokey/src/otp.rs b/nitrokey/src/otp.rs new file mode 100644 index 0000000..00a5e5e --- /dev/null +++ b/nitrokey/src/otp.rs @@ -0,0 +1,407 @@ +use nitrokey_sys; +use std::ffi::CString; +use util::{get_command_result, get_cstring, result_from_string, CommandError}; + +/// Modes for one-time password generation. +#[derive(Debug, PartialEq)] +pub enum OtpMode { + /// Generate one-time passwords with six digits. + SixDigits, + /// Generate one-time passwords with eight digits. + EightDigits, +} + +/// Provides methods to configure and erase OTP slots on a Nitrokey device. +pub trait ConfigureOtp { + /// Configure an HOTP slot with the given data and set the HOTP counter to the given value + /// (default 0). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// - [`InvalidString`][] if the provided token ID contains a null byte + /// - [`NoName`][] if the provided name is empty + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), (CommandError)> { + /// let device = nitrokey::connect()?; + /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); + /// match device.authenticate_admin("12345678") { + /// Ok(admin) => { + /// match admin.write_hotp_slot(slot_data, 0) { + /// Ok(()) => println!("Successfully wrote slot."), + /// Err(err) => println!("Could not write slot: {}", err), + /// } + /// }, + /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`NoName`]: enum.CommandError.html#variant.NoName + fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError>; + + /// Configure a TOTP slot with the given data and set the TOTP time window to the given value + /// (default 30). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// - [`InvalidString`][] if the provided token ID contains a null byte + /// - [`NoName`][] if the provided name is empty + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), (CommandError)> { + /// let device = nitrokey::connect()?; + /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); + /// match device.authenticate_admin("12345678") { + /// Ok(admin) => { + /// match admin.write_totp_slot(slot_data, 30) { + /// Ok(()) => println!("Successfully wrote slot."), + /// Err(err) => println!("Could not write slot: {}", err), + /// } + /// }, + /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`NoName`]: enum.CommandError.html#variant.NoName + fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError>; + + /// Erases an HOTP slot. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, ConfigureOtp}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), (CommandError)> { + /// let device = nitrokey::connect()?; + /// match device.authenticate_admin("12345678") { + /// Ok(admin) => { + /// match admin.erase_hotp_slot(1) { + /// Ok(()) => println!("Successfully erased slot."), + /// Err(err) => println!("Could not erase slot: {}", err), + /// } + /// }, + /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError>; + + /// Erases a TOTP slot. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Authenticate, ConfigureOtp}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), (CommandError)> { + /// let device = nitrokey::connect()?; + /// match device.authenticate_admin("12345678") { + /// Ok(admin) => { + /// match admin.erase_totp_slot(1) { + /// Ok(()) => println!("Successfully erased slot."), + /// Err(err) => println!("Could not erase slot: {}", err), + /// } + /// }, + /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError>; +} + +/// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey +/// device. +pub trait GenerateOtp { + /// Sets the time on the Nitrokey. This command may set the time to arbitrary values. `time` + /// is the number of seconds since January 1st, 1970 (Unix timestamp). + /// + /// The time is used for TOTP generation (see [`get_totp_code`][]). + /// + /// # Example + /// + /// ```ignore + /// extern crate chrono; + /// + /// use chrono::Utc; + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let time = Utc::now().timestamp(); + /// if time < 0 { + /// println!("Timestamps before 1970-01-01 are not supported!"); + /// } else { + /// device.set_time(time as u64); + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// - [`Timestamp`][] if the time could not be set + /// + /// [`get_totp_code`]: #method.get_totp_code + /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp + fn set_time(&self, time: u64) -> Result<(), CommandError> { + unsafe { get_command_result(nitrokey_sys::NK_totp_set_time(time)) } + } + + /// Returns the name of the given HOTP slot. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// - [`SlotNotProgrammed`][] if the given slot is not configured + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{CommandError, GenerateOtp}; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.get_hotp_slot_name(1) { + /// Ok(name) => println!("HOTP slot 1: {}", name), + /// Err(CommandError::SlotNotProgrammed) => println!("HOTP slot 1 not programmed"), + /// Err(err) => println!("Could not get slot name: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed + fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> { + unsafe { result_from_string(nitrokey_sys::NK_get_hotp_slot_name(slot)) } + } + + /// Returns the name of the given TOTP slot. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// - [`SlotNotProgrammed`][] if the given slot is not configured + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{CommandError, GenerateOtp}; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.get_totp_slot_name(1) { + /// Ok(name) => println!("TOTP slot 1: {}", name), + /// Err(CommandError::SlotNotProgrammed) => println!("TOTP slot 1 not programmed"), + /// Err(err) => println!("Could not get slot name: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed + fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> { + unsafe { result_from_string(nitrokey_sys::NK_get_totp_slot_name(slot)) } + } + + /// Generates an HOTP code on the given slot. This operation may require user authorization, + /// depending on the device configuration (see [`get_config`][]). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// - [`NotAuthorized`][] if OTP generation requires user authentication + /// - [`SlotNotProgrammed`][] if the given slot is not configured + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::GenerateOtp; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let code = device.get_hotp_code(1)?; + /// println!("Generated HOTP code on slot 1: {}", code); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`get_config`]: trait.Device.html#method.get_config + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized + /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed + fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> { + unsafe { + return result_from_string(nitrokey_sys::NK_get_hotp_code(slot)); + } + } + + /// Generates a TOTP code on the given slot. This operation may require user authorization, + /// depending on the device configuration (see [`get_config`][]). + /// + /// To make sure that the Nitrokey’s time is in sync, consider calling [`set_time`][] before + /// calling this method. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if there is no slot with the given number + /// - [`NotAuthorized`][] if OTP generation requires user authentication + /// - [`SlotNotProgrammed`][] if the given slot is not configured + /// + /// # Example + /// + /// ```ignore + /// extern crate chrono; + /// + /// use nitrokey::GenerateOtp; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let time = Utc::now().timestamp(); + /// if time < 0 { + /// println!("Timestamps before 1970-01-01 are not supported!"); + /// } else { + /// device.set_time(time as u64); + /// let code = device.get_totp_code(1)?; + /// println!("Generated TOTP code on slot 1: {}", code); + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`set_time`]: #method.set_time + /// [`get_config`]: trait.Device.html#method.get_config + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized + /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed + fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> { + unsafe { + return result_from_string(nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0)); + } + } +} + +/// The configuration for an OTP slot. +#[derive(Debug)] +pub struct OtpSlotData { + /// The number of the slot – must be less than three for HOTP and less than 15 for TOTP. + pub number: u8, + /// The name of the slot – must not be empty. + pub name: String, + /// The secret for the slot. + pub secret: String, + /// The OTP generation mode. + pub mode: OtpMode, + /// If true, press the enter key after sending an OTP code using double-pressed + /// numlock, capslock or scrolllock. + pub use_enter: bool, + /// Set the token ID, see [OATH Token Identifier Specification][tokspec], section “Class A”. + /// + /// [tokspec]: https://openauthentication.org/token-specs/ + pub token_id: Option<String>, +} + +#[derive(Debug)] +pub struct RawOtpSlotData { + pub number: u8, + pub name: CString, + pub secret: CString, + pub mode: OtpMode, + pub use_enter: bool, + pub use_token_id: bool, + pub token_id: CString, +} + +impl OtpSlotData { + /// Constructs a new instance of this struct. + pub fn new<S: Into<String>, T: Into<String>>( + number: u8, + name: S, + secret: T, + mode: OtpMode, + ) -> OtpSlotData { + OtpSlotData { + number, + name: name.into(), + secret: secret.into(), + mode, + use_enter: false, + token_id: None, + } + } + + /// Enables pressing the enter key after sending an OTP code using double-pressed numlock, + /// capslock or scrollock. + pub fn use_enter(mut self) -> OtpSlotData { + self.use_enter = true; + self + } + + /// Sets the token ID, see [OATH Token Identifier Specification][tokspec], section “Class A”. + /// + /// [tokspec]: https://openauthentication.org/token-specs/ + pub fn token_id<S: Into<String>>(mut self, id: S) -> OtpSlotData { + self.token_id = Some(id.into()); + self + } +} + +impl RawOtpSlotData { + pub fn new(data: OtpSlotData) -> Result<RawOtpSlotData, CommandError> { + let name = get_cstring(data.name)?; + let secret = get_cstring(data.secret)?; + let use_token_id = data.token_id.is_some(); + let token_id = get_cstring(data.token_id.unwrap_or_else(String::new))?; + + Ok(RawOtpSlotData { + number: data.number, + name, + secret, + mode: data.mode, + use_enter: data.use_enter, + use_token_id, + token_id, + }) + } +} diff --git a/nitrokey/src/pws.rs b/nitrokey/src/pws.rs new file mode 100644 index 0000000..c20fe9d --- /dev/null +++ b/nitrokey/src/pws.rs @@ -0,0 +1,351 @@ +use device::{Device, DeviceWrapper, Pro, Storage}; +use libc; +use nitrokey_sys; +use util::{get_command_result, get_cstring, get_last_error, result_from_string, CommandError}; + +/// The number of slots in a [`PasswordSafe`][]. +/// +/// [`PasswordSafe`]: struct.PasswordSafe.html +pub const SLOT_COUNT: u8 = 16; + +/// A password safe on a Nitrokey device. +/// +/// The password safe stores a tuple consisting of a name, a login and a password on a slot. The +/// number of available slots is [`SLOT_COUNT`][]. The slots are addressed starting with zero. To +/// retrieve a password safe from a Nitrokey device, use the [`get_password_safe`][] method from +/// the [`GetPasswordSafe`][] trait. Note that the device must live at least as long as the +/// password safe. +/// +/// Once the password safe has been unlocked, it can be accessed without a password. Therefore it +/// is mandatory to call [`lock`][] on the corresponding device after the password store is used. +/// As this command may have side effects on the Nitrokey Storage, it cannot be called +/// automatically once the password safe is destroyed. +/// +/// # Examples +/// +/// Open a password safe and access a password: +/// +/// ```no_run +/// use nitrokey::{Device, GetPasswordSafe, PasswordSafe}; +/// # use nitrokey::CommandError; +/// +/// fn use_password_safe(pws: &PasswordSafe) -> Result<(), CommandError> { +/// let name = pws.get_slot_name(0)?; +/// let login = pws.get_slot_login(0)?; +/// let password = pws.get_slot_login(0)?; +/// println!("Credentials for {}: login {}, password {}", name, login, password); +/// Ok(()) +/// } +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::connect()?; +/// let pws = device.get_password_safe("123456")?; +/// use_password_safe(&pws); +/// device.lock()?; +/// # Ok(()) +/// # } +/// ``` +/// +/// [`SLOT_COUNT`]: constant.SLOT_COUNT.html +/// [`get_password_safe`]: trait.GetPasswordSafe.html#method.get_password_safe +/// [`lock`]: trait.Device.html#method.lock +/// [`GetPasswordSafe`]: trait.GetPasswordSafe.html +pub struct PasswordSafe<'a> { + _device: &'a Device, +} + +/// Provides access to a [`PasswordSafe`][]. +/// +/// The device that implements this trait must always live at least as long as a password safe +/// retrieved from it. +/// +/// [`PasswordSafe`]: struct.PasswordSafe.html +pub trait GetPasswordSafe { + /// Enables and returns the password safe. + /// + /// The underlying device must always live at least as long as a password safe retrieved from + /// it. It is mandatory to lock the underlying device using [`lock`][] after the password safe + /// has been used. Otherwise, other applications can access the password store without + /// authentication. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current user password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{Device, GetPasswordSafe, PasswordSafe}; + /// # use nitrokey::CommandError; + /// + /// fn use_password_safe(pws: &PasswordSafe) {} + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.get_password_safe("123456") { + /// Ok(pws) => { + /// use_password_safe(&pws); + /// device.lock()?; + /// }, + /// Err(err) => println!("Could not open the password safe: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`device`]: struct.PasswordSafe.html#method.device + /// [`lock`]: trait.Device.html#method.lock + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe, CommandError>; +} + +fn get_password_safe<'a>( + device: &'a Device, + user_pin: &str, +) -> Result<PasswordSafe<'a>, CommandError> { + let user_pin_string = get_cstring(user_pin)?; + let result = unsafe { + get_command_result(nitrokey_sys::NK_enable_password_safe( + user_pin_string.as_ptr(), + )) + }; + result.map(|()| PasswordSafe { _device: device }) +} + +impl<'a> PasswordSafe<'a> { + /// Returns the status of all password slots. + /// + /// The status indicates whether a slot is programmed or not. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{GetPasswordSafe, SLOT_COUNT}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let pws = device.get_password_safe("123456")?; + /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { + /// let status = match *programmed { + /// true => "programmed", + /// false => "not programmed", + /// }; + /// println!("Slot {}: {}", slot, status); + /// }); + /// # Ok(()) + /// # } + /// ``` + pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], CommandError> { + let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() }; + if status_ptr.is_null() { + return Err(get_last_error()); + } + let status_array_ptr = status_ptr as *const [u8; SLOT_COUNT as usize]; + let status_array = unsafe { *status_array_ptr }; + let mut result = [false; SLOT_COUNT as usize]; + for i in 0..SLOT_COUNT { + result[i as usize] = status_array[i as usize] == 1; + } + unsafe { + libc::free(status_ptr as *mut libc::c_void); + } + Ok(result) + } + + /// Returns the name of the given slot (if it is programmed). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if the given slot is out of range + /// - [`Unknown`][] if the slot is not programmed + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::GetPasswordSafe; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.get_password_safe("123456") { + /// Ok(pws) => { + /// let name = pws.get_slot_name(0)?; + /// let login = pws.get_slot_login(0)?; + /// let password = pws.get_slot_login(0)?; + /// println!("Credentials for {}: login {}, password {}", name, login, password); + /// }, + /// Err(err) => println!("Could not open the password safe: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`Unknown`]: enum.CommandError.html#variant.Unknown + pub fn get_slot_name(&self, slot: u8) -> Result<String, CommandError> { + unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } + } + + /// Returns the login for the given slot (if it is programmed). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if the given slot is out of range + /// - [`Unknown`][] if the slot is not programmed + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::GetPasswordSafe; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let pws = device.get_password_safe("123456")?; + /// let name = pws.get_slot_name(0)?; + /// let login = pws.get_slot_login(0)?; + /// let password = pws.get_slot_login(0)?; + /// println!("Credentials for {}: login {}, password {}", name, login, password); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`Unknown`]: enum.CommandError.html#variant.Unknown + pub fn get_slot_login(&self, slot: u8) -> Result<String, CommandError> { + unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } + } + + /// Returns the password for the given slot (if it is programmed). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if the given slot is out of range + /// - [`Unknown`][] if the slot is not programmed + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::GetPasswordSafe; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let pws = device.get_password_safe("123456")?; + /// let name = pws.get_slot_name(0)?; + /// let login = pws.get_slot_login(0)?; + /// let password = pws.get_slot_login(0)?; + /// println!("Credentials for {}: login {}, password {}", name, login, password); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`Unknown`]: enum.CommandError.html#variant.Unknown + pub fn get_slot_password(&self, slot: u8) -> Result<String, CommandError> { + unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } + } + + /// Writes the given slot with the given name, login and password. + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if the given slot is out of range + /// - [`InvalidString`][] if the provided token ID contains a null byte + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::GetPasswordSafe; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let pws = device.get_password_safe("123456")?; + /// let name = pws.get_slot_name(0)?; + /// let login = pws.get_slot_login(0)?; + /// let password = pws.get_slot_login(0)?; + /// println!("Credentials for {}: login {}, password {}", name, login, password); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + pub fn write_slot( + &self, + slot: u8, + name: &str, + login: &str, + password: &str, + ) -> Result<(), CommandError> { + let name_string = get_cstring(name)?; + let login_string = get_cstring(login)?; + let password_string = get_cstring(password)?; + unsafe { + get_command_result(nitrokey_sys::NK_write_password_safe_slot( + slot, + name_string.as_ptr(), + login_string.as_ptr(), + password_string.as_ptr(), + )) + } + } + + /// Erases the given slot. Erasing clears the stored name, login and password (if the slot was + /// programmed). + /// + /// # Errors + /// + /// - [`InvalidSlot`][] if the given slot is out of range + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::GetPasswordSafe; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// let pws = device.get_password_safe("123456")?; + /// match pws.erase_slot(0) { + /// Ok(()) => println!("Erased slot 0."), + /// Err(err) => println!("Could not erase slot 0: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + pub fn erase_slot(&self, slot: u8) -> Result<(), CommandError> { + unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) } + } +} + +impl<'a> Drop for PasswordSafe<'a> { + 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(&self, user_pin: &str) -> Result<PasswordSafe, CommandError> { + get_password_safe(self, user_pin) + } +} + +impl GetPasswordSafe for Storage { + fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe, CommandError> { + get_password_safe(self, user_pin) + } +} + +impl GetPasswordSafe for DeviceWrapper { + fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe, CommandError> { + get_password_safe(self, user_pin) + } +} diff --git a/nitrokey/src/tests/device.rs b/nitrokey/src/tests/device.rs new file mode 100644 index 0000000..fed465d --- /dev/null +++ b/nitrokey/src/tests/device.rs @@ -0,0 +1,304 @@ +use std::ffi::CStr; +use std::process::Command; +use std::{thread, time}; +use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; +use {Authenticate, CommandError, Config, Device, Storage}; + +static ADMIN_NEW_PASSWORD: &str = "1234567890"; +static USER_NEW_PASSWORD: &str = "abcdefghij"; + +fn count_nitrokey_block_devices() -> usize { + thread::sleep(time::Duration::from_secs(2)); + let output = Command::new("lsblk") + .args(&["-o", "MODEL"]) + .output() + .expect("Could not list block devices"); + String::from_utf8_lossy(&output.stdout) + .split("\n") + .filter(|&s| s == "Nitrokey Storage") + .count() +} + +#[test] +#[cfg_attr(not(feature = "test-no-device"), ignore)] +fn connect_no_device() { + assert!(::connect().is_err()); + assert!(::Pro::connect().is_err()); + assert!(::Storage::connect().is_err()); +} + +#[test] +#[cfg_attr(not(feature = "test-pro"), ignore)] +fn connect_pro() { + assert!(::connect().is_ok()); + assert!(::Pro::connect().is_ok()); + assert!(::Storage::connect().is_err()); + match ::connect().unwrap() { + ::DeviceWrapper::Pro(_) => assert!(true), + ::DeviceWrapper::Storage(_) => assert!(false), + }; +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn connect_storage() { + assert!(::connect().is_ok()); + assert!(::Pro::connect().is_err()); + assert!(::Storage::connect().is_ok()); + match ::connect().unwrap() { + ::DeviceWrapper::Pro(_) => assert!(false), + ::DeviceWrapper::Storage(_) => assert!(true), + }; +} + +fn assert_empty_serial_number() { + unsafe { + let ptr = ::nitrokey_sys::NK_device_serial_number(); + assert!(!ptr.is_null()); + let cstr = CStr::from_ptr(ptr); + assert_eq!(cstr.to_string_lossy(), ""); + } +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn disconnect() { + Target::connect().unwrap(); + assert_empty_serial_number(); + Target::connect() + .unwrap() + .authenticate_admin(ADMIN_PASSWORD) + .unwrap(); + assert_empty_serial_number(); + Target::connect() + .unwrap() + .authenticate_user(USER_PASSWORD) + .unwrap(); + assert_empty_serial_number(); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_serial_number() { + let device = Target::connect().unwrap(); + let result = device.get_serial_number(); + assert!(result.is_ok()); + let serial_number = result.unwrap(); + assert!(serial_number.is_ascii()); + assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit())); +} +#[test] +#[cfg_attr(not(feature = "test-pro"), ignore)] +fn get_firmware_version() { + let device = Target::connect().unwrap(); + assert_eq!(0, device.get_major_firmware_version()); + let minor = device.get_minor_firmware_version(); + assert!(minor > 0); +} + +fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T { + let result = device.authenticate_admin(&(ADMIN_PASSWORD.to_owned() + suffix)); + let device = match result { + Ok(admin) => admin.device(), + Err((device, _)) => device, + }; + assert_eq!(count, device.get_admin_retry_count()); + return device; +} + +fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T { + let result = device.authenticate_user(&(USER_PASSWORD.to_owned() + suffix)); + let device = match result { + Ok(admin) => admin.device(), + Err((device, _)) => device, + }; + assert_eq!(count, device.get_user_retry_count()); + return device; +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_retry_count() { + let device = Target::connect().unwrap(); + + let device = admin_retry(device, "", 3); + let device = admin_retry(device, "123", 2); + let device = admin_retry(device, "456", 1); + let device = admin_retry(device, "", 3); + + let device = user_retry(device, "", 3); + let device = user_retry(device, "123", 2); + let device = user_retry(device, "456", 1); + user_retry(device, "", 3); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn config() { + let device = Target::connect().unwrap(); + let admin = device.authenticate_admin(ADMIN_PASSWORD).unwrap(); + let config = Config::new(None, None, None, true); + assert!(admin.write_config(config).is_ok()); + let get_config = admin.get_config().unwrap(); + assert_eq!(config, get_config); + + let config = Config::new(None, Some(9), None, true); + assert_eq!(Err(CommandError::InvalidSlot), admin.write_config(config)); + + let config = Config::new(Some(1), None, Some(0), false); + assert!(admin.write_config(config).is_ok()); + let get_config = admin.get_config().unwrap(); + assert_eq!(config, get_config); + + let config = Config::new(None, None, None, false); + assert!(admin.write_config(config).is_ok()); + let get_config = admin.get_config().unwrap(); + assert_eq!(config, get_config); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn change_user_pin() { + let device = Target::connect().unwrap(); + let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; + + assert!( + device + .change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD) + .is_ok() + ); + + let device = device.authenticate_user(USER_PASSWORD).unwrap_err().0; + let device = device + .authenticate_user(USER_NEW_PASSWORD) + .unwrap() + .device(); + + let result = device.change_user_pin(USER_PASSWORD, USER_PASSWORD); + assert_eq!(Err(CommandError::WrongPassword), result); + + assert!( + device + .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD) + .is_ok() + ); + + let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + assert!(device.authenticate_user(USER_NEW_PASSWORD).is_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn change_admin_pin() { + let device = Target::connect().unwrap(); + let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + let device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; + + assert!( + device + .change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD) + .is_ok() + ); + + let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap_err().0; + let device = device + .authenticate_admin(ADMIN_NEW_PASSWORD) + .unwrap() + .device(); + + assert_eq!( + Err(CommandError::WrongPassword), + device.change_admin_pin(ADMIN_PASSWORD, ADMIN_PASSWORD) + ); + + assert!( + device + .change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD) + .is_ok() + ); + + let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); +} + +fn require_failed_user_login(device: Target, password: &str, error: CommandError) -> Target { + let result = device.authenticate_user(password); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(error, err.1); + err.0 +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn unlock_user_pin() { + let device = Target::connect().unwrap(); + let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + assert!( + device + .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) + .is_ok() + ); + assert_eq!( + Err(CommandError::WrongPassword), + device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) + ); + + let wrong_password = USER_PASSWORD.to_owned() + "foo"; + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + + assert_eq!( + Err(CommandError::WrongPassword), + device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) + ); + assert!( + device + .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) + .is_ok() + ); + device.authenticate_user(USER_PASSWORD).unwrap(); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn encrypted_volume() { + let device = Storage::connect().unwrap(); + assert!(device.lock().is_ok()); + + assert_eq!(1, count_nitrokey_block_devices()); + assert!(device.disable_encrypted_volume().is_ok()); + assert_eq!(1, count_nitrokey_block_devices()); + assert_eq!( + Err(CommandError::WrongPassword), + device.enable_encrypted_volume("123") + ); + assert_eq!(1, count_nitrokey_block_devices()); + assert!(device.enable_encrypted_volume(USER_PASSWORD).is_ok()); + assert_eq!(2, count_nitrokey_block_devices()); + assert!(device.disable_encrypted_volume().is_ok()); + assert_eq!(1, count_nitrokey_block_devices()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn lock() { + let device = Storage::connect().unwrap(); + + assert!(device.enable_encrypted_volume(USER_PASSWORD).is_ok()); + assert!(device.lock().is_ok()); + assert_eq!(1, count_nitrokey_block_devices()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn get_storage_status() { + let device = Storage::connect().unwrap(); + let status = device.get_status().unwrap(); + + assert!(status.serial_number_sd_card > 0); + assert!(status.serial_number_smart_card > 0); +} diff --git a/nitrokey/src/tests/mod.rs b/nitrokey/src/tests/mod.rs new file mode 100644 index 0000000..34ca0aa --- /dev/null +++ b/nitrokey/src/tests/mod.rs @@ -0,0 +1,4 @@ +mod device; +mod otp; +mod pws; +mod util; diff --git a/nitrokey/src/tests/otp.rs b/nitrokey/src/tests/otp.rs new file mode 100644 index 0000000..cf71d9d --- /dev/null +++ b/nitrokey/src/tests/otp.rs @@ -0,0 +1,283 @@ +use std::ops::Deref; +use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; +use {Admin, Authenticate, CommandError, Config, ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; + +// test suite according to RFC 4226, Appendix D +static HOTP_SECRET: &str = "3132333435363738393031323334353637383930"; +static HOTP_CODES: &[&str] = &[ + "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", + "520489", +]; + +// test suite according to RFC 6238, Appendix B +static TOTP_SECRET: &str = "3132333435363738393031323334353637383930"; +static TOTP_CODES: &[(u64, &str)] = &[ + (59, "94287082"), + (1111111109, "07081804"), + (1111111111, "14050471"), + (1234567890, "89005924"), + (2000000000, "69279037"), + (20000000000, "65353130"), +]; + +#[derive(PartialEq)] +enum TotpTimestampSize { + U32, + U64, +} + +fn get_admin_test_device() -> Admin<Target> { + Target::connect() + .expect("Could not connect to the Nitrokey.") + .authenticate_admin(ADMIN_PASSWORD) + .expect("Could not login as admin.") +} + +fn configure_hotp(admin: &ConfigureOtp, counter: u8) { + let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); + assert!(admin.write_hotp_slot(slot_data, counter.into()).is_ok()); +} + +fn check_hotp_codes(device: &GenerateOtp, offset: u8) { + HOTP_CODES.iter().enumerate().for_each(|(i, code)| { + if i >= offset as usize { + let result = device.get_hotp_code(1); + assert_eq!(code, &result.unwrap()); + } + }); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn hotp_no_pin() { + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, false); + assert!(admin.write_config(config).is_ok()); + + configure_hotp(&admin, 0); + check_hotp_codes(admin.deref(), 0); + + configure_hotp(&admin, 5); + check_hotp_codes(admin.deref(), 5); + + configure_hotp(&admin, 0); + check_hotp_codes(&admin.device(), 0); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn hotp_pin() { + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, true); + assert!(admin.write_config(config).is_ok()); + + configure_hotp(&admin, 0); + let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); + check_hotp_codes(&user, 0); + + assert!(user.device().get_hotp_code(1).is_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn hotp_slot_name() { + let admin = get_admin_test_device(); + let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); + assert!(admin.write_hotp_slot(slot_data, 0).is_ok()); + + let device = admin.device(); + let result = device.get_hotp_slot_name(1); + assert_eq!("test-hotp", result.unwrap()); + let result = device.get_hotp_slot_name(4); + assert_eq!(CommandError::InvalidSlot, result.unwrap_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn hotp_error() { + let admin = get_admin_test_device(); + let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); + assert_eq!( + Err(CommandError::NoName), + admin.write_hotp_slot(slot_data, 0) + ); + let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); + assert_eq!( + Err(CommandError::InvalidSlot), + admin.write_hotp_slot(slot_data, 0) + ); + let code = admin.get_hotp_code(4); + assert_eq!(CommandError::InvalidSlot, code.unwrap_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn hotp_erase() { + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, false); + assert!(admin.write_config(config).is_ok()); + let slot_data = OtpSlotData::new(1, "test1", HOTP_SECRET, OtpMode::SixDigits); + assert!(admin.write_hotp_slot(slot_data, 0).is_ok()); + let slot_data = OtpSlotData::new(2, "test2", HOTP_SECRET, OtpMode::SixDigits); + assert!(admin.write_hotp_slot(slot_data, 0).is_ok()); + + assert!(admin.erase_hotp_slot(1).is_ok()); + + let device = admin.device(); + let result = device.get_hotp_slot_name(1); + assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); + let result = device.get_hotp_code(1); + assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); + + assert_eq!("test2", device.get_hotp_slot_name(2).unwrap()); +} + +fn configure_totp(admin: &ConfigureOtp, factor: u64) { + let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); + let time_window = 30u64.checked_mul(factor).unwrap(); + assert!(admin.write_totp_slot(slot_data, time_window as u16).is_ok()); +} + +fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimestampSize) { + for (i, &(base_time, code)) in TOTP_CODES.iter().enumerate() { + let time = base_time.checked_mul(factor).unwrap(); + let is_u64 = time > u32::max_value() as u64; + if is_u64 != (timestamp_size == TotpTimestampSize::U64) { + continue; + } + + assert!(device.set_time(time).is_ok()); + let result = device.get_totp_code(1); + assert!(result.is_ok()); + let result_code = result.unwrap(); + assert_eq!( + code, result_code, + "TOTP code {} should be {} but is {}", + i, code, result_code + ); + } +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn totp_no_pin() { + // TODO: this test may fail due to bad timing --> find solution + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, false); + assert!(admin.write_config(config).is_ok()); + + configure_totp(&admin, 1); + check_totp_codes(admin.deref(), 1, TotpTimestampSize::U32); + + configure_totp(&admin, 2); + check_totp_codes(admin.deref(), 2, TotpTimestampSize::U32); + + configure_totp(&admin, 1); + check_totp_codes(&admin.device(), 1, TotpTimestampSize::U32); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +#[cfg_attr(feature = "test-storage", should_panic(expected = "assertion failed"))] +// Nitrokey Storage does only support timestamps that fit in a 32-bit unsigned integer. Therefore +// the last RFC test case is expected to fail. +fn totp_no_pin_64() { + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, false); + assert!(admin.write_config(config).is_ok()); + + configure_totp(&admin, 1); + check_totp_codes(admin.deref(), 1, TotpTimestampSize::U64); + + configure_totp(&admin, 2); + check_totp_codes(admin.deref(), 2, TotpTimestampSize::U64); + + configure_totp(&admin, 1); + check_totp_codes(&admin.device(), 1, TotpTimestampSize::U64); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn totp_pin() { + // TODO: this test may fail due to bad timing --> find solution + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, true); + assert!(admin.write_config(config).is_ok()); + + configure_totp(&admin, 1); + let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); + check_totp_codes(&user, 1, TotpTimestampSize::U32); + + assert!(user.device().get_totp_code(1).is_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +#[cfg_attr(feature = "test-storage", should_panic(expected = "assertion failed"))] +// See comment for totp_no_pin_64. +fn totp_pin_64() { + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, true); + assert!(admin.write_config(config).is_ok()); + + configure_totp(&admin, 1); + let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); + check_totp_codes(&user, 1, TotpTimestampSize::U64); + + assert!(user.device().get_totp_code(1).is_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn totp_slot_name() { + let admin = get_admin_test_device(); + let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); + assert!(admin.write_totp_slot(slot_data, 0).is_ok()); + + let device = admin.device(); + let result = device.get_totp_slot_name(1); + assert!(result.is_ok()); + assert_eq!("test-totp", result.unwrap()); + let result = device.get_totp_slot_name(16); + assert_eq!(CommandError::InvalidSlot, result.unwrap_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn totp_error() { + let admin = get_admin_test_device(); + let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); + assert_eq!( + Err(CommandError::NoName), + admin.write_hotp_slot(slot_data, 0) + ); + let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); + assert_eq!( + Err(CommandError::InvalidSlot), + admin.write_hotp_slot(slot_data, 0) + ); + let code = admin.get_hotp_code(4); + assert_eq!(CommandError::InvalidSlot, code.unwrap_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn totp_erase() { + let admin = get_admin_test_device(); + let config = Config::new(None, None, None, false); + assert!(admin.write_config(config).is_ok()); + let slot_data = OtpSlotData::new(1, "test1", TOTP_SECRET, OtpMode::SixDigits); + assert!(admin.write_totp_slot(slot_data, 0).is_ok()); + let slot_data = OtpSlotData::new(2, "test2", TOTP_SECRET, OtpMode::SixDigits); + assert!(admin.write_totp_slot(slot_data, 0).is_ok()); + + assert!(admin.erase_totp_slot(1).is_ok()); + + let device = admin.device(); + let result = device.get_totp_slot_name(1); + assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); + let result = device.get_totp_code(1); + assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); + + assert_eq!("test2", device.get_totp_slot_name(2).unwrap()); +} diff --git a/nitrokey/src/tests/pws.rs b/nitrokey/src/tests/pws.rs new file mode 100644 index 0000000..f581515 --- /dev/null +++ b/nitrokey/src/tests/pws.rs @@ -0,0 +1,143 @@ +use device::Device; +use nitrokey_sys; +use pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; +use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; +use util::{result_from_string, CommandError}; + +fn get_pws(device: &Target) -> PasswordSafe { + device.get_password_safe(USER_PASSWORD).unwrap() +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn enable() { + let device = Target::connect().unwrap(); + assert!( + device + .get_password_safe(&(USER_PASSWORD.to_owned() + "123")) + .is_err() + ); + assert!(device.get_password_safe(USER_PASSWORD).is_ok()); + assert!(device.get_password_safe(ADMIN_PASSWORD).is_err()); + assert!(device.get_password_safe(USER_PASSWORD).is_ok()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn drop() { + let device = Target::connect().unwrap(); + { + let pws = get_pws(&device); + assert!(pws.write_slot(1, "name", "login", "password").is_ok()); + assert_eq!("name", pws.get_slot_name(1).unwrap()); + let result = result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(1) }); + assert_eq!(Ok(String::from("name")), result); + } + let result = result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(1) }); + assert_eq!(Ok(String::from("name")), result); + assert!(device.lock().is_ok()); + let result = result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(1) }); + assert_eq!(Err(CommandError::NotAuthorized), result); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_status() { + let device = Target::connect().unwrap(); + let pws = get_pws(&device); + for i in 0..SLOT_COUNT { + assert!(pws.erase_slot(i).is_ok(), "Could not erase slot {}", i); + } + let status = pws.get_slot_status().unwrap(); + assert_eq!(status, [false; SLOT_COUNT as usize]); + + assert!(pws.write_slot(1, "name", "login", "password").is_ok()); + let status = pws.get_slot_status().unwrap(); + for i in 0..SLOT_COUNT { + assert_eq!(i == 1, status[i as usize]); + } + + for i in 0..SLOT_COUNT { + assert!(pws.write_slot(i, "name", "login", "password").is_ok()); + } + let status = pws.get_slot_status().unwrap(); + assert_eq!(status, [true; SLOT_COUNT as usize]); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_data() { + let device = Target::connect().unwrap(); + let pws = get_pws(&device); + assert!(pws.write_slot(1, "name", "login", "password").is_ok()); + assert_eq!("name", pws.get_slot_name(1).unwrap()); + assert_eq!("login", pws.get_slot_login(1).unwrap()); + assert_eq!("password", pws.get_slot_password(1).unwrap()); + + assert!(pws.erase_slot(1).is_ok()); + // TODO: check error codes + assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(1)); + assert_eq!(Err(CommandError::Unknown), pws.get_slot_login(1)); + assert_eq!(Err(CommandError::Unknown), pws.get_slot_password(1)); + + let name = "with å"; + let login = "pär@test.com"; + let password = "'i3lJc[09?I:,[u7dWz9"; + assert!(pws.write_slot(1, name, login, password).is_ok()); + assert_eq!(name, pws.get_slot_name(1).unwrap()); + assert_eq!(login, pws.get_slot_login(1).unwrap()); + assert_eq!(password, pws.get_slot_password(1).unwrap()); + + assert_eq!( + Err(CommandError::InvalidSlot), + pws.get_slot_name(SLOT_COUNT) + ); + assert_eq!( + Err(CommandError::InvalidSlot), + pws.get_slot_login(SLOT_COUNT) + ); + assert_eq!( + Err(CommandError::InvalidSlot), + pws.get_slot_password(SLOT_COUNT) + ); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn write() { + let device = Target::connect().unwrap(); + let pws = get_pws(&device); + + assert_eq!( + Err(CommandError::InvalidSlot), + pws.write_slot(SLOT_COUNT, "name", "login", "password") + ); + + assert!(pws.write_slot(0, "", "login", "password").is_ok()); + assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(0)); + assert_eq!(Ok(String::from("login")), pws.get_slot_login(0)); + assert_eq!(Ok(String::from("password")), pws.get_slot_password(0)); + + assert!(pws.write_slot(0, "name", "", "password").is_ok()); + assert_eq!(Ok(String::from("name")), pws.get_slot_name(0)); + assert_eq!(Err(CommandError::Unknown), pws.get_slot_login(0)); + assert_eq!(Ok(String::from("password")), pws.get_slot_password(0)); + + assert!(pws.write_slot(0, "name", "login", "").is_ok()); + assert_eq!(Ok(String::from("name")), pws.get_slot_name(0)); + assert_eq!(Ok(String::from("login")), pws.get_slot_login(0)); + assert_eq!(Err(CommandError::Unknown), pws.get_slot_password(0)); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn erase() { + let device = Target::connect().unwrap(); + let pws = get_pws(&device); + assert_eq!(Err(CommandError::InvalidSlot), pws.erase_slot(SLOT_COUNT)); + + assert!(pws.write_slot(0, "name", "login", "password").is_ok()); + assert!(pws.erase_slot(0).is_ok()); + assert!(pws.erase_slot(0).is_ok()); + assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(0)); +} diff --git a/nitrokey/src/tests/util.rs b/nitrokey/src/tests/util.rs new file mode 100644 index 0000000..c6fbb8f --- /dev/null +++ b/nitrokey/src/tests/util.rs @@ -0,0 +1,11 @@ +pub static ADMIN_PASSWORD: &str = "12345678"; +pub static USER_PASSWORD: &str = "123456"; + +#[cfg(feature = "test-no-device")] +pub type Target = ::Pro; + +#[cfg(feature = "test-pro")] +pub type Target = ::Pro; + +#[cfg(feature = "test-storage")] +pub type Target = ::Storage; diff --git a/nitrokey/src/util.rs b/nitrokey/src/util.rs new file mode 100644 index 0000000..6f4fbb0 --- /dev/null +++ b/nitrokey/src/util.rs @@ -0,0 +1,171 @@ +use libc::{c_void, free}; +use nitrokey_sys; +use rand::{OsRng, Rng}; +use std; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::os::raw::{c_char, c_int}; + +/// Error types returned by Nitrokey device or by the library. +#[derive(Debug, PartialEq)] +pub enum CommandError { + /// A packet with a wrong checksum has been sent or received. + WrongCrc, + /// A command tried to access an OTP slot that does not exist. + WrongSlot, + /// A command tried to generate an OTP on a slot that is not configured. + SlotNotProgrammed, + /// The provided password is wrong. + WrongPassword, + /// You are not authorized for this command or provided a wrong temporary + /// password. + NotAuthorized, + /// An error occured when getting or setting the time. + Timestamp, + /// You did not provide a name for the OTP slot. + NoName, + /// This command is not supported by this device. + NotSupported, + /// This command is unknown. + UnknownCommand, + /// AES decryption failed. + AesDecryptionFailed, + /// An unknown error occured. + Unknown, + /// You passed a string containing a null byte. + InvalidString, + /// You passed an invalid slot. + InvalidSlot, + /// An error occured during random number generation. + RngError, +} + +/// Log level for libnitrokey. +/// +/// Setting the log level to a lower level enables all output from higher levels too. Currently, +/// only the log levels `Warning`, `DebugL1`, `Debug` and `DebugL2` are actually used. +#[derive(Debug, PartialEq)] +pub enum LogLevel { + /// Error messages. Currently not used. + Error, + /// Warning messages. + Warning, + /// Informational messages. Currently not used. + Info, + /// Basic debug messages, especially basic information on the sent and received packets. + DebugL1, + /// Detailed debug messages, especially detailed information on the sent and received packets. + Debug, + /// Very detailed debug messages, especially detailed information about the control flow for + /// device communication (for example function entries and exits). + DebugL2, +} + +pub fn owned_str_from_ptr(ptr: *const c_char) -> String { + unsafe { + return CStr::from_ptr(ptr).to_string_lossy().into_owned(); + } +} + +pub fn result_from_string(ptr: *const c_char) -> Result<String, CommandError> { + if ptr.is_null() { + return Err(CommandError::Unknown); + } + unsafe { + let s = owned_str_from_ptr(ptr); + free(ptr as *mut c_void); + if s.is_empty() { + return Err(get_last_error()); + } + return Ok(s); + } +} + +pub fn get_command_result(value: c_int) -> Result<(), CommandError> { + match value { + 0 => Ok(()), + other => Err(CommandError::from(other)), + } +} + +pub fn get_last_result() -> Result<(), CommandError> { + let value = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int; + get_command_result(value) +} + +pub fn get_last_error() -> CommandError { + return match get_last_result() { + Ok(()) => CommandError::Unknown, + Err(err) => err, + }; +} + +pub fn generate_password(length: usize) -> std::io::Result<Vec<u8>> { + let mut rng = match OsRng::new() { + Ok(rng) => rng, + Err(err) => return Err(err), + }; + let mut data = vec![0u8; length]; + rng.fill_bytes(&mut data[..]); + return Ok(data); +} + +pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, CommandError> { + CString::new(s).or(Err(CommandError::InvalidString)) +} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let msg = match *self { + CommandError::WrongCrc => "A packet with a wrong checksum has been sent or received", + CommandError::WrongSlot => "The given OTP slot does not exist", + CommandError::SlotNotProgrammed => "The given OTP slot is not programmed", + CommandError::WrongPassword => "The given password is wrong", + CommandError::NotAuthorized => { + "You are not authorized for this command or provided a wrong temporary password" + } + CommandError::Timestamp => "An error occured when getting or setting the time", + CommandError::NoName => "You did not provide a name for the OTP slot", + CommandError::NotSupported => "This command is not supported by this device", + CommandError::UnknownCommand => "This command is unknown", + CommandError::AesDecryptionFailed => "AES decryption failed", + CommandError::Unknown => "An unknown error occured", + CommandError::InvalidString => "You passed a string containing a null byte", + CommandError::InvalidSlot => "The given slot is invalid", + CommandError::RngError => "An error occured during random number generation", + }; + write!(f, "{}", msg) + } +} + +impl From<c_int> for CommandError { + fn from(value: c_int) -> Self { + match value { + 1 => CommandError::WrongCrc, + 2 => CommandError::WrongSlot, + 3 => CommandError::SlotNotProgrammed, + 4 => CommandError::WrongPassword, + 5 => CommandError::NotAuthorized, + 6 => CommandError::Timestamp, + 7 => CommandError::NoName, + 8 => CommandError::NotSupported, + 9 => CommandError::UnknownCommand, + 10 => CommandError::AesDecryptionFailed, + 201 => CommandError::InvalidSlot, + _ => CommandError::Unknown, + } + } +} + +impl Into<i32> for LogLevel { + fn into(self) -> i32 { + match self { + LogLevel::Error => 0, + LogLevel::Warning => 1, + LogLevel::Info => 2, + LogLevel::DebugL1 => 3, + LogLevel::Debug => 4, + LogLevel::DebugL2 => 5, + } + } +} diff --git a/rand/.gitignore b/rand/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/rand/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/rand/.travis.yml b/rand/.travis.yml new file mode 100644 index 0000000..f3d7688 --- /dev/null +++ b/rand/.travis.yml @@ -0,0 +1,33 @@ +language: rust +sudo: false + +matrix: + include: + - rust: 1.15.0 + - rust: stable + - rust: stable + os: osx + - rust: beta + - rust: nightly + + - rust: nightly + before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + script: + - cargo doc --no-deps --all-features + - cargo test --benches + - cargo test --features nightly + after_success: + - travis-cargo --only nightly doc-upload + +script: + - cargo test + - cargo test --manifest-path rand-derive/Cargo.toml + +env: + global: + secure: "BdDntVHSompN+Qxz5Rz45VI4ZqhD72r6aPl166FADlnkIwS6N6FLWdqs51O7G5CpoMXEDvyYrjmRMZe/GYLIG9cmqmn/wUrWPO+PauGiIuG/D2dmfuUNvSTRcIe7UQLXrfP3yyfZPgqsH6pSnNEVopquQKy3KjzqepgriOJtbyY=" + +notifications: + email: + on_success: never diff --git a/rand/CHANGELOG.md b/rand/CHANGELOG.md new file mode 100644 index 0000000..1811b45 --- /dev/null +++ b/rand/CHANGELOG.md @@ -0,0 +1,269 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [0.4.3] - 2018-08-16 +### Fixed +- Use correct syscall number for PowerPC (#589) + +## [0.4.2] - 2018-01-05 +### Changed +- Use winapi on Windows +- Update for Fuchsia OS +- Remove dev-dependency on `log` + +## [0.4.1] - 2017-12-17 +### Added +- `no_std` support + +## [0.4.0-pre.0] - 2017-12-11 +### Added +- `JitterRng` added as a high-quality alternative entropy source using the + system timer +- new `seq` module with `sample_iter`, `sample_slice`, etc. +- WASM support via dummy implementations (fail at run-time) +- Additional benchmarks, covering generators and new seq code + +### Changed +- `thread_rng` uses `JitterRng` if seeding from system time fails + (slower but more secure than previous method) + +### Deprecated + - `sample` function deprecated (replaced by `sample_iter`) + +## [0.3.18] - 2017-11-06 +### Changed +- `thread_rng` is seeded from the system time if `OsRng` fails +- `weak_rng` now uses `thread_rng` internally + + +## [0.3.17] - 2017-10-07 +### Changed + - Fuchsia: Magenta was renamed Zircon + +## [0.3.16] - 2017-07-27 +### Added +- Implement Debug for mote non-public types +- implement `Rand` for (i|u)i128 +- Support for Fuchsia + +### Changed +- Add inline attribute to SampleRange::construct_range. + This improves the benchmark for sample in 11% and for shuffle in 16%. +- Use `RtlGenRandom` instead of `CryptGenRandom` + + +## [0.3.15] - 2016-11-26 +### Added +- Add `Rng` trait method `choose_mut` +- Redox support + +### Changed +- Use `arc4rand` for `OsRng` on FreeBSD. +- Use `arc4random(3)` for `OsRng` on OpenBSD. + +### Fixed +- Fix filling buffers 4 GiB or larger with `OsRng::fill_bytes` on Windows + + +## [0.3.14] - 2016-02-13 +### Fixed +- Inline definitions from winapi/advapi32, wich decreases build times + + +## [0.3.13] - 2016-01-09 +### Fixed +- Compatible with Rust 1.7.0-nightly (needed some extra type annotations) + + +## [0.3.12] - 2015-11-09 +### Changed +- Replaced the methods in `next_f32` and `next_f64` with the technique described + Saito & Matsumoto at MCQMC'08. The new method should exhibit a slightly more + uniform distribution. +- Depend on libc 0.2 + +### Fixed +- Fix iterator protocol issue in `rand::sample` + + +## [0.3.11] - 2015-08-31 +### Added +- Implement `Rand` for arrays with n <= 32 + + +## [0.3.10] - 2015-08-17 +### Added +- Support for NaCl platforms + +### Changed +- Allow `Rng` to be `?Sized`, impl for `&mut R` and `Box<R>` where `R: ?Sized + Rng` + + +## [0.3.9] - 2015-06-18 +### Changed +- Use `winapi` for Windows API things + +### Fixed +- Fixed test on stable/nightly +- Fix `getrandom` syscall number for aarch64-unknown-linux-gnu + + +## [0.3.8] - 2015-04-23 +### Changed +- `log` is a dev dependency + +### Fixed +- Fix race condition of atomics in `is_getrandom_available` + + +## [0.3.7] - 2015-04-03 +### Fixed +- Derive Copy/Clone changes + + +## [0.3.6] - 2015-04-02 +### Changed +- Move to stable Rust! + + +## [0.3.5] - 2015-04-01 +### Fixed +- Compatible with Rust master + + +## [0.3.4] - 2015-03-31 +### Added +- Implement Clone for `Weighted` + +### Fixed +- Compatible with Rust master + + +## [0.3.3] - 2015-03-26 +### Fixed +- Fix compile on Windows + + +## [0.3.2] - 2015-03-26 + + +## [0.3.1] - 2015-03-26 +### Fixed +- Fix compile on Windows + + +## [0.3.0] - 2015-03-25 +### Changed +- Update to use log version 0.3.x + + +## [0.2.1] - 2015-03-22 +### Fixed +- Compatible with Rust master +- Fixed iOS compilation + + +## [0.2.0] - 2015-03-06 +### Fixed +- Compatible with Rust master (move from `old_io` to `std::io`) + + +## [0.1.4] - 2015-03-04 +### Fixed +- Compatible with Rust master (use wrapping ops) + + +## [0.1.3] - 2015-02-20 +### Fixed +- Compatible with Rust master + +### Removed +- Removed Copy inplementaions from RNGs + + +## [0.1.2] - 2015-02-03 +### Added +- Imported functionality from `std::rand`, including: + - `StdRng`, `SeedableRng`, `TreadRng`, `weak_rng()` + - `ReaderRng`: A wrapper around any Reader to treat it as an RNG. +- Imported documentation from `std::rand` +- Imported tests from `std::rand` + + +## [0.1.1] - 2015-02-03 +### Added +- Migrate to a cargo-compatible directory structure. + +### Fixed +- Do not use entropy during `gen_weighted_bool(1)` + + +## [Rust 0.12.0] - 2014-10-09 +### Added +- Impl Rand for tuples of arity 11 and 12 +- Include ChaCha pseudorandom generator +- Add `next_f64` and `next_f32` to Rng +- Implement Clone for PRNGs + +### Changed +- Rename `TaskRng` to `ThreadRng` and `task_rng` to `thread_rng` (since a + runtime is removed from Rust). + +### Fixed +- Improved performance of ISAAC and ISAAC64 by 30% and 12 % respectively, by + informing the optimiser that indexing is never out-of-bounds. + +### Removed +- Removed the Deprecated `choose_option` + + +## [Rust 0.11.0] - 2014-07-02 +### Added +- document when to use `OSRng` in cryptographic context, and explain why we use `/dev/urandom` instead of `/dev/random` +- `Rng::gen_iter()` which will return an infinite stream of random values +- `Rng::gen_ascii_chars()` which will return an infinite stream of random ascii characters + +### Changed +- Now only depends on libcore! 2adf5363f88ffe06f6d2ea5c338d1b186d47f4a1 +- Remove `Rng.choose()`, rename `Rng.choose_option()` to `.choose()` +- Rename OSRng to OsRng +- The WeightedChoice structure is no longer built with a `Vec<Weighted<T>>`, + but rather a `&mut [Weighted<T>]`. This means that the WeightedChoice + structure now has a lifetime associated with it. +- The `sample` method on `Rng` has been moved to a top-level function in the + `rand` module due to its dependence on `Vec`. + +### Removed +- `Rng::gen_vec()` was removed. Previous behavior can be regained with + `rng.gen_iter().take(n).collect()` +- `Rng::gen_ascii_str()` was removed. Previous behavior can be regained with + `rng.gen_ascii_chars().take(n).collect()` +- {IsaacRng, Isaac64Rng, XorShiftRng}::new() have all been removed. These all + relied on being able to use an OSRng for seeding, but this is no longer + available in librand (where these types are defined). To retain the same + functionality, these types now implement the `Rand` trait so they can be + generated with a random seed from another random number generator. This allows + the stdlib to use an OSRng to create seeded instances of these RNGs. +- Rand implementations for `Box<T>` and `@T` were removed. These seemed to be + pretty rare in the codebase, and it allows for librand to not depend on + liballoc. Additionally, other pointer types like Rc<T> and Arc<T> were not + supported. +- Remove a slew of old deprecated functions + + +## [Rust 0.10] - 2014-04-03 +### Changed +- replace `Rng.shuffle's` functionality with `.shuffle_mut` +- bubble up IO errors when creating an OSRng + +### Fixed +- Use `fill()` instead of `read()` +- Rewrite OsRng in Rust for windows + +## [0.10-pre] - 2014-03-02 +### Added +- Seperate `rand` out of the standard library + diff --git a/rand/Cargo.toml b/rand/Cargo.toml new file mode 100644 index 0000000..c21f53e --- /dev/null +++ b/rand/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "rand" +version = "0.4.3" +authors = ["The Rust Project Developers"] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/rust-lang-nursery/rand" +documentation = "https://docs.rs/rand" +homepage = "https://github.com/rust-lang-nursery/rand" +description = """ +Random number generators and other randomness functionality. +""" +keywords = ["random", "rng"] +categories = ["algorithms"] + +[features] +default = ["std"] +nightly = ["i128_support"] # enables all features requiring nightly rust + +std = ["libc"] # default feature; without this rand uses libcore +alloc = [] # enables Vec and Box support without std + +i128_support = [] # enables i128 and u128 support + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2", optional = true } + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["minwindef", "ntsecapi", "profileapi", "winnt"] } + +[workspace] +members = ["rand-derive"] + +[target.'cfg(target_os = "fuchsia")'.dependencies] +fuchsia-zircon = "0.3.2" diff --git a/rand/LICENSE-APACHE b/rand/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/rand/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/rand/LICENSE-MIT b/rand/LICENSE-MIT new file mode 100644 index 0000000..39d4bdb --- /dev/null +++ b/rand/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 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/rand/README.md b/rand/README.md new file mode 100644 index 0000000..f72bd51 --- /dev/null +++ b/rand/README.md @@ -0,0 +1,139 @@ +rand +==== + +A Rust library for random number generators and other randomness functionality. + +[![Build Status](https://travis-ci.org/rust-lang-nursery/rand.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/rand) +[![Build status](https://ci.appveyor.com/api/projects/status/rm5c9o33k3jhchbw?svg=true)](https://ci.appveyor.com/project/alexcrichton/rand) + +[Documentation](https://docs.rs/rand) + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +rand = "0.4" +``` + +and this to your crate root: + +```rust +extern crate rand; +``` + +### Versions + +Version `0.4`was released in December 2017. It contains almost no breaking +changes since the `0.3` series, but nevertheless contains some significant +new code, including a new "external" entropy source (`JitterRng`) and `no_std` +support. + +Version `0.5` is in development and contains significant performance +improvements for the ISAAC random number generators. + +## Examples + +There is built-in support for a random number generator (RNG) associated with each thread stored in thread-local storage. This RNG can be accessed via thread_rng, or used implicitly via random. This RNG is normally randomly seeded from an operating-system source of randomness, e.g. /dev/urandom on Unix systems, and will automatically reseed itself from this source after generating 32 KiB of random data. + +```rust +let tuple = rand::random::<(f64, char)>(); +println!("{:?}", tuple) +``` + +```rust +use rand::Rng; + +let mut rng = rand::thread_rng(); +if rng.gen() { // random bool + println!("i32: {}, u32: {}", rng.gen::<i32>(), rng.gen::<u32>()) +} +``` + +It is also possible to use other RNG types, which have a similar interface. The following uses the "ChaCha" algorithm instead of the default. + +```rust +use rand::{Rng, ChaChaRng}; + +let mut rng = rand::ChaChaRng::new_unseeded(); +println!("i32: {}, u32: {}", rng.gen::<i32>(), rng.gen::<u32>()) +``` + +## Features + +By default, `rand` is built with all stable features available. The following +optional features are available: + +- `i128_support` enables support for generating `u128` and `i128` values +- `nightly` enables all unstable features (`i128_support`) +- `std` enabled by default; by setting "default-features = false" `no_std` + mode is activated; this removes features depending on `std` functionality: + + - `OsRng` is entirely unavailable + - `JitterRng` code is still present, but a nanosecond timer must be + provided via `JitterRng::new_with_timer` + - Since no external entropy is available, it is not possible to create + generators with fresh seeds (user must provide entropy) + - `thread_rng`, `weak_rng` and `random` are all disabled + - exponential, normal and gamma type distributions are unavailable + since `exp` and `log` functions are not provided in `core` + - any code requiring `Vec` or `Box` +- `alloc` can be used instead of `std` to provide `Vec` and `Box` + +## Testing + +Unfortunately, `cargo test` does not test everything. The following tests are +recommended: + +``` +# Basic tests for rand and sub-crates +cargo test --all + +# Test no_std support (build only since nearly all tests require std) +cargo build --all --no-default-features + +# Test 128-bit support (requires nightly) +cargo test --all --features nightly + +# Benchmarks (requires nightly) +cargo bench +# or just to test the benchmark code: +cargo test --benches +``` + +# `derive(Rand)` + +You can derive the `Rand` trait for your custom type via the `#[derive(Rand)]` +directive. To use this first add this to your Cargo.toml: + +```toml +rand = "0.4" +rand_derive = "0.3" +``` + +Next in your crate: + +```rust +extern crate rand; +#[macro_use] +extern crate rand_derive; + +#[derive(Rand, Debug)] +struct MyStruct { + a: i32, + b: u32, +} + +fn main() { + println!("{:?}", rand::random::<MyStruct>()); +} +``` + + +# License + +`rand` is primarily distributed under the terms of both the MIT +license and the Apache License (Version 2.0). + +See LICENSE-APACHE, and LICENSE-MIT for details. diff --git a/rand/appveyor.yml b/rand/appveyor.yml new file mode 100644 index 0000000..02e217f --- /dev/null +++ b/rand/appveyor.yml @@ -0,0 +1,38 @@ +environment: + + # At the time this was added AppVeyor was having troubles with checking + # revocation of SSL certificates of sites like static.rust-lang.org and what + # we think is crates.io. The libcurl HTTP client by default checks for + # revocation on Windows and according to a mailing list [1] this can be + # disabled. + # + # The `CARGO_HTTP_CHECK_REVOKE` env var here tells cargo to disable SSL + # revocation checking on Windows in libcurl. Note, though, that rustup, which + # we're using to download Rust here, also uses libcurl as the default backend. + # Unlike Cargo, however, rustup doesn't have a mechanism to disable revocation + # checking. To get rustup working we set `RUSTUP_USE_HYPER` which forces it to + # use the Hyper instead of libcurl backend. Both Hyper and libcurl use + # schannel on Windows but it appears that Hyper configures it slightly + # differently such that revocation checking isn't turned on by default. + # + # [1]: https://curl.haxx.se/mail/lib-2016-03/0202.html + RUSTUP_USE_HYPER: 1 + CARGO_HTTP_CHECK_REVOKE: false + + matrix: + - TARGET: x86_64-pc-windows-msvc + - TARGET: i686-pc-windows-msvc +install: + - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init.exe -y --default-host %TARGET% --default-toolchain nightly + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo test --benches + - cargo test + - cargo test --features nightly + - cargo test --manifest-path rand-derive/Cargo.toml diff --git a/rand/benches/bench.rs b/rand/benches/bench.rs new file mode 100644 index 0000000..d396f25 --- /dev/null +++ b/rand/benches/bench.rs @@ -0,0 +1,34 @@ +#![feature(test)] + +extern crate test; +extern crate rand; + +const RAND_BENCH_N: u64 = 1000; + +mod distributions; + +use std::mem::size_of; +use test::{black_box, Bencher}; +use rand::{StdRng, Rng}; + +#[bench] +fn rand_f32(b: &mut Bencher) { + let mut rng = StdRng::new().unwrap(); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.next_f32()); + } + }); + b.bytes = size_of::<f32>() as u64 * RAND_BENCH_N; +} + +#[bench] +fn rand_f64(b: &mut Bencher) { + let mut rng = StdRng::new().unwrap(); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.next_f64()); + } + }); + b.bytes = size_of::<f64>() as u64 * RAND_BENCH_N; +} diff --git a/rand/benches/distributions/exponential.rs b/rand/benches/distributions/exponential.rs new file mode 100644 index 0000000..152615d --- /dev/null +++ b/rand/benches/distributions/exponential.rs @@ -0,0 +1,18 @@ +use std::mem::size_of; +use test::Bencher; +use rand; +use rand::distributions::exponential::Exp; +use rand::distributions::Sample; + +#[bench] +fn rand_exp(b: &mut Bencher) { + let mut rng = rand::weak_rng(); + let mut exp = Exp::new(2.71828 * 3.14159); + + b.iter(|| { + for _ in 0..::RAND_BENCH_N { + exp.sample(&mut rng); + } + }); + b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N; +} diff --git a/rand/benches/distributions/gamma.rs b/rand/benches/distributions/gamma.rs new file mode 100644 index 0000000..bf3fd36 --- /dev/null +++ b/rand/benches/distributions/gamma.rs @@ -0,0 +1,31 @@ +use std::mem::size_of; +use test::Bencher; +use rand; +use rand::distributions::IndependentSample; +use rand::distributions::gamma::Gamma; + +#[bench] +fn bench_gamma_large_shape(b: &mut Bencher) { + let gamma = Gamma::new(10., 1.0); + let mut rng = rand::weak_rng(); + + b.iter(|| { + for _ in 0..::RAND_BENCH_N { + gamma.ind_sample(&mut rng); + } + }); + b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N; +} + +#[bench] +fn bench_gamma_small_shape(b: &mut Bencher) { + let gamma = Gamma::new(0.1, 1.0); + let mut rng = rand::weak_rng(); + + b.iter(|| { + for _ in 0..::RAND_BENCH_N { + gamma.ind_sample(&mut rng); + } + }); + b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N; +} diff --git a/rand/benches/distributions/mod.rs b/rand/benches/distributions/mod.rs new file mode 100644 index 0000000..49f6bd9 --- /dev/null +++ b/rand/benches/distributions/mod.rs @@ -0,0 +1,3 @@ +mod exponential; +mod normal; +mod gamma; diff --git a/rand/benches/distributions/normal.rs b/rand/benches/distributions/normal.rs new file mode 100644 index 0000000..1c858b1 --- /dev/null +++ b/rand/benches/distributions/normal.rs @@ -0,0 +1,18 @@ +use std::mem::size_of; +use test::Bencher; +use rand; +use rand::distributions::Sample; +use rand::distributions::normal::Normal; + +#[bench] +fn rand_normal(b: &mut Bencher) { + let mut rng = rand::weak_rng(); + let mut normal = Normal::new(-2.71828, 3.14159); + + b.iter(|| { + for _ in 0..::RAND_BENCH_N { + normal.sample(&mut rng); + } + }); + b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N; +} diff --git a/rand/benches/generators.rs b/rand/benches/generators.rs new file mode 100644 index 0000000..daee7c5 --- /dev/null +++ b/rand/benches/generators.rs @@ -0,0 +1,133 @@ +#![feature(test)] + +extern crate test; +extern crate rand; + +const RAND_BENCH_N: u64 = 1000; +const BYTES_LEN: usize = 1024; + +use std::mem::size_of; +use test::{black_box, Bencher}; + +use rand::{Rng, StdRng, OsRng, JitterRng}; +use rand::{XorShiftRng, IsaacRng, Isaac64Rng, ChaChaRng}; + +macro_rules! gen_bytes { + ($fnn:ident, $gen:ident) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng: $gen = OsRng::new().unwrap().gen(); + let mut buf = [0u8; BYTES_LEN]; + b.iter(|| { + for _ in 0..RAND_BENCH_N { + rng.fill_bytes(&mut buf); + black_box(buf); + } + }); + b.bytes = BYTES_LEN as u64 * RAND_BENCH_N; + } + } +} + +macro_rules! gen_bytes_new { + ($fnn:ident, $gen:ident) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng = $gen::new().unwrap(); + let mut buf = [0u8; BYTES_LEN]; + b.iter(|| { + for _ in 0..RAND_BENCH_N { + rng.fill_bytes(&mut buf); + black_box(buf); + } + }); + b.bytes = BYTES_LEN as u64 * RAND_BENCH_N; + } + } +} + +gen_bytes!(gen_bytes_xorshift, XorShiftRng); +gen_bytes!(gen_bytes_isaac, IsaacRng); +gen_bytes!(gen_bytes_isaac64, Isaac64Rng); +gen_bytes!(gen_bytes_chacha, ChaChaRng); +gen_bytes_new!(gen_bytes_std, StdRng); +gen_bytes_new!(gen_bytes_os, OsRng); + + +macro_rules! gen_uint { + ($fnn:ident, $ty:ty, $gen:ident) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng: $gen = OsRng::new().unwrap().gen(); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.gen::<$ty>()); + } + }); + b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; + } + } +} + +macro_rules! gen_uint_new { + ($fnn:ident, $ty:ty, $gen:ident) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng = $gen::new().unwrap(); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.gen::<$ty>()); + } + }); + b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; + } + } +} + +gen_uint!(gen_u32_xorshift, u32, XorShiftRng); +gen_uint!(gen_u32_isaac, u32, IsaacRng); +gen_uint!(gen_u32_isaac64, u32, Isaac64Rng); +gen_uint!(gen_u32_chacha, u32, ChaChaRng); +gen_uint_new!(gen_u32_std, u32, StdRng); +gen_uint_new!(gen_u32_os, u32, OsRng); + +gen_uint!(gen_u64_xorshift, u64, XorShiftRng); +gen_uint!(gen_u64_isaac, u64, IsaacRng); +gen_uint!(gen_u64_isaac64, u64, Isaac64Rng); +gen_uint!(gen_u64_chacha, u64, ChaChaRng); +gen_uint_new!(gen_u64_std, u64, StdRng); +gen_uint_new!(gen_u64_os, u64, OsRng); + +#[bench] +fn gen_u64_jitter(b: &mut Bencher) { + let mut rng = JitterRng::new().unwrap(); + b.iter(|| { + black_box(rng.gen::<u64>()); + }); + b.bytes = size_of::<u64>() as u64; +} + +macro_rules! init_gen { + ($fnn:ident, $gen:ident) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng: XorShiftRng = OsRng::new().unwrap().gen(); + b.iter(|| { + let r2: $gen = rng.gen(); + black_box(r2); + }); + } + } +} + +init_gen!(init_xorshift, XorShiftRng); +init_gen!(init_isaac, IsaacRng); +init_gen!(init_isaac64, Isaac64Rng); +init_gen!(init_chacha, ChaChaRng); + +#[bench] +fn init_jitter(b: &mut Bencher) { + b.iter(|| { + black_box(JitterRng::new().unwrap()); + }); +} diff --git a/rand/benches/misc.rs b/rand/benches/misc.rs new file mode 100644 index 0000000..4251761 --- /dev/null +++ b/rand/benches/misc.rs @@ -0,0 +1,62 @@ +#![feature(test)] + +extern crate test; +extern crate rand; + +use test::{black_box, Bencher}; + +use rand::{Rng, weak_rng}; +use rand::seq::*; + +#[bench] +fn misc_shuffle_100(b: &mut Bencher) { + let mut rng = weak_rng(); + let x : &mut [usize] = &mut [1; 100]; + b.iter(|| { + rng.shuffle(x); + black_box(&x); + }) +} + +#[bench] +fn misc_sample_iter_10_of_100(b: &mut Bencher) { + let mut rng = weak_rng(); + let x : &[usize] = &[1; 100]; + b.iter(|| { + black_box(sample_iter(&mut rng, x, 10).unwrap_or_else(|e| e)); + }) +} + +#[bench] +fn misc_sample_slice_10_of_100(b: &mut Bencher) { + let mut rng = weak_rng(); + let x : &[usize] = &[1; 100]; + b.iter(|| { + black_box(sample_slice(&mut rng, x, 10)); + }) +} + +#[bench] +fn misc_sample_slice_ref_10_of_100(b: &mut Bencher) { + let mut rng = weak_rng(); + let x : &[usize] = &[1; 100]; + b.iter(|| { + black_box(sample_slice_ref(&mut rng, x, 10)); + }) +} + +macro_rules! sample_indices { + ($name:ident, $amount:expr, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = weak_rng(); + b.iter(|| { + black_box(sample_indices(&mut rng, $length, $amount)); + }) + } + } +} + +sample_indices!(misc_sample_indices_10_of_1k, 10, 1000); +sample_indices!(misc_sample_indices_50_of_1k, 50, 1000); +sample_indices!(misc_sample_indices_100_of_1k, 100, 1000); diff --git a/rand/rand-derive/Cargo.toml b/rand/rand-derive/Cargo.toml new file mode 100644 index 0000000..1a2dbe1 --- /dev/null +++ b/rand/rand-derive/Cargo.toml @@ -0,0 +1,23 @@ +[package] + +name = "rand_derive" +version = "0.3.1" +authors = ["The Rust Project Developers"] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/rust-lang-nursery/rand" +documentation = "https://docs.rs/rand_derive" +homepage = "https://github.com/rust-lang-nursery/rand" +description = """ +`#[derive(Rand)]` functionality for the `rand::Rand` trait. +""" + +[lib] +proc-macro = true + +[dependencies] +quote = "0.3" +syn = "0.11" + +[dev-dependencies] +rand = { path = "..", version = "0.4" } diff --git a/rand/rand-derive/README.md b/rand/rand-derive/README.md new file mode 100644 index 0000000..3d1fedb --- /dev/null +++ b/rand/rand-derive/README.md @@ -0,0 +1,51 @@ + +rand_macros +==== + +`#[derive(Rand)]` functionality for the `rand::Rand` trait. + +## Usage +Add this to your `Cargo.toml`: + +```toml +[dependencies] +rand = "0.4" +rand_macros = "0.2" +``` + +and this to your crate root: + +```rust +extern crate rand; +#[macro_use] +extern crate rand_macros; +``` + +## Examples + +`#[derive(Rand)]` can be used on any `struct` or `enum` where all fields/variants implement `rand::Rand`. + +```rust +#[derive(Debug, Rand)] +struct Foo { + x: u16, + y: Option<f64>, +} + +#[derive(Debug, Rand)] +enum Bar { + X{x: u8, y: isize}, + Y([bool; 4]), + Z, +} +``` +Now you can call the `Rng::gen()` function on your custom types. + +```rust +use rand::Rng; + +let mut rng = rand::thread_rng(); + +println!("{:?}", rng.gen::<Foo>()); +println!("{:?}", rng.gen::<Bar>()); +``` diff --git a/rand/rand-derive/src/lib.rs b/rand/rand-derive/src/lib.rs new file mode 100644 index 0000000..80c803a --- /dev/null +++ b/rand/rand-derive/src/lib.rs @@ -0,0 +1,116 @@ +//! Support for `#[derive(Rand)]` +//! +//! # Examples +//! +//! ``` +//! extern crate rand; +//! #[macro_use] +//! extern crate rand_derive; +//! +//! #[derive(Rand, Debug)] +//! struct MyStruct { +//! a: i32, +//! b: u32, +//! } +//! +//! fn main() { +//! println!("{:?}", rand::random::<MyStruct>()); +//! } +//! ``` + +extern crate proc_macro; +#[macro_use] +extern crate quote; +extern crate syn; + +use proc_macro::TokenStream; + +#[proc_macro_derive(Rand)] +pub fn rand_derive(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + let gen = impl_rand_derive(&ast); + gen.parse().unwrap() +} + +fn impl_rand_derive(ast: &syn::MacroInput) -> quote::Tokens { + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let rand = match ast.body { + syn::Body::Struct(syn::VariantData::Struct(ref body)) => { + let fields = body + .iter() + .filter_map(|field| field.ident.as_ref()) + .map(|ident| quote! { #ident: __rng.gen() }) + .collect::<Vec<_>>(); + + quote! { #name { #(#fields,)* } } + }, + syn::Body::Struct(syn::VariantData::Tuple(ref body)) => { + let fields = (0..body.len()) + .map(|_| quote! { __rng.gen() }) + .collect::<Vec<_>>(); + + quote! { #name (#(#fields),*) } + }, + syn::Body::Struct(syn::VariantData::Unit) => { + quote! { #name } + }, + syn::Body::Enum(ref body) => { + if body.is_empty() { + panic!("`Rand` cannot be derived for enums with no variants"); + } + + let len = body.len(); + let mut arms = body + .iter() + .map(|variant| { + let ident = &variant.ident; + match variant.data { + syn::VariantData::Struct(ref body) => { + let fields = body + .iter() + .filter_map(|field| field.ident.as_ref()) + .map(|ident| quote! { #ident: __rng.gen() }) + .collect::<Vec<_>>(); + quote! { #name::#ident { #(#fields,)* } } + }, + syn::VariantData::Tuple(ref body) => { + let fields = (0..body.len()) + .map(|_| quote! { __rng.gen() }) + .collect::<Vec<_>>(); + + quote! { #name::#ident (#(#fields),*) } + }, + syn::VariantData::Unit => quote! { #name::#ident } + } + }); + + match len { + 1 => quote! { #(#arms)* }, + 2 => { + let (a, b) = (arms.next(), arms.next()); + quote! { if __rng.gen() { #a } else { #b } } + }, + _ => { + let mut variants = arms + .enumerate() + .map(|(index, arm)| quote! { #index => #arm }) + .collect::<Vec<_>>(); + variants.push(quote! { _ => unreachable!() }); + quote! { match __rng.gen_range(0, #len) { #(#variants,)* } } + }, + } + } + }; + + quote! { + impl #impl_generics ::rand::Rand for #name #ty_generics #where_clause { + #[inline] + fn rand<__R: ::rand::Rng>(__rng: &mut __R) -> Self { + #rand + } + } + } +} diff --git a/rand/rand-derive/tests/rand_macros.rs b/rand/rand-derive/tests/rand_macros.rs new file mode 100644 index 0000000..938f2b0 --- /dev/null +++ b/rand/rand-derive/tests/rand_macros.rs @@ -0,0 +1,58 @@ +#![allow(dead_code)] + +extern crate rand; +#[macro_use] +extern crate rand_derive; + +use rand::Rng; + +#[derive(Rand)] +struct Struct { + x: u16, + y: Option<f64>, +} + +#[derive(Rand)] +struct Tuple(i16, Option<f64>); + +#[derive(Rand)] +struct Unit; + +#[derive(Rand)] +enum EnumUnit { + X, +} + +#[derive(Rand)] +enum Enum1 { + X(u8, f32), +} + +#[derive(Rand)] +enum Enum2 { + X(bool), + Y, +} + +#[derive(Rand)] +enum Enum3 { + X { x: u8, y: isize }, + Y([bool; 4]), + Z, +} + +#[test] +fn smoke() { + let mut rng = rand::XorShiftRng::new_unseeded(); + + // check nothing horrible happens internally: + for _ in 0..100 { + let _ = rng.gen::<Struct>(); + let _ = rng.gen::<Tuple>(); + let _ = rng.gen::<Unit>(); + let _ = rng.gen::<EnumUnit>(); + let _ = rng.gen::<Enum1>(); + let _ = rng.gen::<Enum2>(); + let _ = rng.gen::<Enum3>(); + } +} diff --git a/rand/src/distributions/exponential.rs b/rand/src/distributions/exponential.rs new file mode 100644 index 0000000..c3c924c --- /dev/null +++ b/rand/src/distributions/exponential.rs @@ -0,0 +1,124 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! The exponential distribution. + +use {Rng, Rand}; +use distributions::{ziggurat, ziggurat_tables, Sample, IndependentSample}; + +/// A wrapper around an `f64` to generate Exp(1) random numbers. +/// +/// See `Exp` for the general exponential distribution. +/// +/// Implemented via the ZIGNOR variant[1] of the Ziggurat method. The +/// exact description in the paper was adjusted to use tables for the +/// exponential distribution rather than normal. +/// +/// [1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to +/// Generate Normal Random +/// Samples*](http://www.doornik.com/research/ziggurat.pdf). Nuffield +/// College, Oxford +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::exponential::Exp1; +/// +/// let Exp1(x) = rand::random(); +/// println!("{}", x); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Exp1(pub f64); + +// This could be done via `-rng.gen::<f64>().ln()` but that is slower. +impl Rand for Exp1 { + #[inline] + fn rand<R:Rng>(rng: &mut R) -> Exp1 { + #[inline] + fn pdf(x: f64) -> f64 { + (-x).exp() + } + #[inline] + fn zero_case<R:Rng>(rng: &mut R, _u: f64) -> f64 { + ziggurat_tables::ZIG_EXP_R - rng.gen::<f64>().ln() + } + + Exp1(ziggurat(rng, false, + &ziggurat_tables::ZIG_EXP_X, + &ziggurat_tables::ZIG_EXP_F, + pdf, zero_case)) + } +} + +/// The exponential distribution `Exp(lambda)`. +/// +/// This distribution has density function: `f(x) = lambda * +/// exp(-lambda * x)` for `x > 0`. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{Exp, IndependentSample}; +/// +/// let exp = Exp::new(2.0); +/// let v = exp.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from a Exp(2) distribution", v); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Exp { + /// `lambda` stored as `1/lambda`, since this is what we scale by. + lambda_inverse: f64 +} + +impl Exp { + /// Construct a new `Exp` with the given shape parameter + /// `lambda`. Panics if `lambda <= 0`. + #[inline] + pub fn new(lambda: f64) -> Exp { + assert!(lambda > 0.0, "Exp::new called with `lambda` <= 0"); + Exp { lambda_inverse: 1.0 / lambda } + } +} + +impl Sample<f64> for Exp { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl IndependentSample<f64> for Exp { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + let Exp1(n) = rng.gen::<Exp1>(); + n * self.lambda_inverse + } +} + +#[cfg(test)] +mod test { + use distributions::{Sample, IndependentSample}; + use super::Exp; + + #[test] + fn test_exp() { + let mut exp = Exp::new(10.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + assert!(exp.sample(&mut rng) >= 0.0); + assert!(exp.ind_sample(&mut rng) >= 0.0); + } + } + #[test] + #[should_panic] + fn test_exp_invalid_lambda_zero() { + Exp::new(0.0); + } + #[test] + #[should_panic] + fn test_exp_invalid_lambda_neg() { + Exp::new(-10.0); + } +} diff --git a/rand/src/distributions/gamma.rs b/rand/src/distributions/gamma.rs new file mode 100644 index 0000000..2806495 --- /dev/null +++ b/rand/src/distributions/gamma.rs @@ -0,0 +1,386 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. +// +// ignore-lexer-test FIXME #15679 + +//! The Gamma and derived distributions. + +use self::GammaRepr::*; +use self::ChiSquaredRepr::*; + +use {Rng, Open01}; +use super::normal::StandardNormal; +use super::{IndependentSample, Sample, Exp}; + +/// The Gamma distribution `Gamma(shape, scale)` distribution. +/// +/// The density function of this distribution is +/// +/// ```text +/// f(x) = x^(k - 1) * exp(-x / θ) / (Γ(k) * θ^k) +/// ``` +/// +/// where `Γ` is the Gamma function, `k` is the shape and `θ` is the +/// scale and both `k` and `θ` are strictly positive. +/// +/// The algorithm used is that described by Marsaglia & Tsang 2000[1], +/// falling back to directly sampling from an Exponential for `shape +/// == 1`, and using the boosting technique described in [1] for +/// `shape < 1`. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{IndependentSample, Gamma}; +/// +/// let gamma = Gamma::new(2.0, 5.0); +/// let v = gamma.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from a Gamma(2, 5) distribution", v); +/// ``` +/// +/// [1]: George Marsaglia and Wai Wan Tsang. 2000. "A Simple Method +/// for Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 +/// (September 2000), +/// 363-372. DOI:[10.1145/358407.358414](http://doi.acm.org/10.1145/358407.358414) +#[derive(Clone, Copy, Debug)] +pub struct Gamma { + repr: GammaRepr, +} + +#[derive(Clone, Copy, Debug)] +enum GammaRepr { + Large(GammaLargeShape), + One(Exp), + Small(GammaSmallShape) +} + +// These two helpers could be made public, but saving the +// match-on-Gamma-enum branch from using them directly (e.g. if one +// knows that the shape is always > 1) doesn't appear to be much +// faster. + +/// Gamma distribution where the shape parameter is less than 1. +/// +/// Note, samples from this require a compulsory floating-point `pow` +/// call, which makes it significantly slower than sampling from a +/// gamma distribution where the shape parameter is greater than or +/// equal to 1. +/// +/// See `Gamma` for sampling from a Gamma distribution with general +/// shape parameters. +#[derive(Clone, Copy, Debug)] +struct GammaSmallShape { + inv_shape: f64, + large_shape: GammaLargeShape +} + +/// Gamma distribution where the shape parameter is larger than 1. +/// +/// See `Gamma` for sampling from a Gamma distribution with general +/// shape parameters. +#[derive(Clone, Copy, Debug)] +struct GammaLargeShape { + scale: f64, + c: f64, + d: f64 +} + +impl Gamma { + /// Construct an object representing the `Gamma(shape, scale)` + /// distribution. + /// + /// Panics if `shape <= 0` or `scale <= 0`. + #[inline] + pub fn new(shape: f64, scale: f64) -> Gamma { + assert!(shape > 0.0, "Gamma::new called with shape <= 0"); + assert!(scale > 0.0, "Gamma::new called with scale <= 0"); + + let repr = if shape == 1.0 { + One(Exp::new(1.0 / scale)) + } else if shape < 1.0 { + Small(GammaSmallShape::new_raw(shape, scale)) + } else { + Large(GammaLargeShape::new_raw(shape, scale)) + }; + Gamma { repr: repr } + } +} + +impl GammaSmallShape { + fn new_raw(shape: f64, scale: f64) -> GammaSmallShape { + GammaSmallShape { + inv_shape: 1. / shape, + large_shape: GammaLargeShape::new_raw(shape + 1.0, scale) + } + } +} + +impl GammaLargeShape { + fn new_raw(shape: f64, scale: f64) -> GammaLargeShape { + let d = shape - 1. / 3.; + GammaLargeShape { + scale: scale, + c: 1. / (9. * d).sqrt(), + d: d + } + } +} + +impl Sample<f64> for Gamma { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl Sample<f64> for GammaSmallShape { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl Sample<f64> for GammaLargeShape { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} + +impl IndependentSample<f64> for Gamma { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + match self.repr { + Small(ref g) => g.ind_sample(rng), + One(ref g) => g.ind_sample(rng), + Large(ref g) => g.ind_sample(rng), + } + } +} +impl IndependentSample<f64> for GammaSmallShape { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + let Open01(u) = rng.gen::<Open01<f64>>(); + + self.large_shape.ind_sample(rng) * u.powf(self.inv_shape) + } +} +impl IndependentSample<f64> for GammaLargeShape { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + loop { + let StandardNormal(x) = rng.gen::<StandardNormal>(); + let v_cbrt = 1.0 + self.c * x; + if v_cbrt <= 0.0 { // a^3 <= 0 iff a <= 0 + continue + } + + let v = v_cbrt * v_cbrt * v_cbrt; + let Open01(u) = rng.gen::<Open01<f64>>(); + + let x_sqr = x * x; + if u < 1.0 - 0.0331 * x_sqr * x_sqr || + u.ln() < 0.5 * x_sqr + self.d * (1.0 - v + v.ln()) { + return self.d * v * self.scale + } + } + } +} + +/// The chi-squared distribution `χ²(k)`, where `k` is the degrees of +/// freedom. +/// +/// For `k > 0` integral, this distribution is the sum of the squares +/// of `k` independent standard normal random variables. For other +/// `k`, this uses the equivalent characterisation +/// `χ²(k) = Gamma(k/2, 2)`. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{ChiSquared, IndependentSample}; +/// +/// let chi = ChiSquared::new(11.0); +/// let v = chi.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from a χ²(11) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct ChiSquared { + repr: ChiSquaredRepr, +} + +#[derive(Clone, Copy, Debug)] +enum ChiSquaredRepr { + // k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1, + // e.g. when alpha = 1/2 as it would be for this case, so special- + // casing and using the definition of N(0,1)^2 is faster. + DoFExactlyOne, + DoFAnythingElse(Gamma), +} + +impl ChiSquared { + /// Create a new chi-squared distribution with degrees-of-freedom + /// `k`. Panics if `k < 0`. + pub fn new(k: f64) -> ChiSquared { + let repr = if k == 1.0 { + DoFExactlyOne + } else { + assert!(k > 0.0, "ChiSquared::new called with `k` < 0"); + DoFAnythingElse(Gamma::new(0.5 * k, 2.0)) + }; + ChiSquared { repr: repr } + } +} +impl Sample<f64> for ChiSquared { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl IndependentSample<f64> for ChiSquared { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + match self.repr { + DoFExactlyOne => { + // k == 1 => N(0,1)^2 + let StandardNormal(norm) = rng.gen::<StandardNormal>(); + norm * norm + } + DoFAnythingElse(ref g) => g.ind_sample(rng) + } + } +} + +/// The Fisher F distribution `F(m, n)`. +/// +/// This distribution is equivalent to the ratio of two normalised +/// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) / +/// (χ²(n)/n)`. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{FisherF, IndependentSample}; +/// +/// let f = FisherF::new(2.0, 32.0); +/// let v = f.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from an F(2, 32) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct FisherF { + numer: ChiSquared, + denom: ChiSquared, + // denom_dof / numer_dof so that this can just be a straight + // multiplication, rather than a division. + dof_ratio: f64, +} + +impl FisherF { + /// Create a new `FisherF` distribution, with the given + /// parameter. Panics if either `m` or `n` are not positive. + pub fn new(m: f64, n: f64) -> FisherF { + assert!(m > 0.0, "FisherF::new called with `m < 0`"); + assert!(n > 0.0, "FisherF::new called with `n < 0`"); + + FisherF { + numer: ChiSquared::new(m), + denom: ChiSquared::new(n), + dof_ratio: n / m + } + } +} +impl Sample<f64> for FisherF { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl IndependentSample<f64> for FisherF { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + self.numer.ind_sample(rng) / self.denom.ind_sample(rng) * self.dof_ratio + } +} + +/// The Student t distribution, `t(nu)`, where `nu` is the degrees of +/// freedom. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{StudentT, IndependentSample}; +/// +/// let t = StudentT::new(11.0); +/// let v = t.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from a t(11) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct StudentT { + chi: ChiSquared, + dof: f64 +} + +impl StudentT { + /// Create a new Student t distribution with `n` degrees of + /// freedom. Panics if `n <= 0`. + pub fn new(n: f64) -> StudentT { + assert!(n > 0.0, "StudentT::new called with `n <= 0`"); + StudentT { + chi: ChiSquared::new(n), + dof: n + } + } +} +impl Sample<f64> for StudentT { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl IndependentSample<f64> for StudentT { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + let StandardNormal(norm) = rng.gen::<StandardNormal>(); + norm * (self.dof / self.chi.ind_sample(rng)).sqrt() + } +} + +#[cfg(test)] +mod test { + use distributions::{Sample, IndependentSample}; + use super::{ChiSquared, StudentT, FisherF}; + + #[test] + fn test_chi_squared_one() { + let mut chi = ChiSquared::new(1.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + chi.sample(&mut rng); + chi.ind_sample(&mut rng); + } + } + #[test] + fn test_chi_squared_small() { + let mut chi = ChiSquared::new(0.5); + let mut rng = ::test::rng(); + for _ in 0..1000 { + chi.sample(&mut rng); + chi.ind_sample(&mut rng); + } + } + #[test] + fn test_chi_squared_large() { + let mut chi = ChiSquared::new(30.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + chi.sample(&mut rng); + chi.ind_sample(&mut rng); + } + } + #[test] + #[should_panic] + fn test_chi_squared_invalid_dof() { + ChiSquared::new(-1.0); + } + + #[test] + fn test_f() { + let mut f = FisherF::new(2.0, 32.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + f.sample(&mut rng); + f.ind_sample(&mut rng); + } + } + + #[test] + fn test_t() { + let mut t = StudentT::new(11.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + t.sample(&mut rng); + t.ind_sample(&mut rng); + } + } +} diff --git a/rand/src/distributions/mod.rs b/rand/src/distributions/mod.rs new file mode 100644 index 0000000..5de8efb --- /dev/null +++ b/rand/src/distributions/mod.rs @@ -0,0 +1,409 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Sampling from random distributions. +//! +//! This is a generalization of `Rand` to allow parameters to control the +//! exact properties of the generated values, e.g. the mean and standard +//! deviation of a normal distribution. The `Sample` trait is the most +//! general, and allows for generating values that change some state +//! internally. The `IndependentSample` trait is for generating values +//! that do not need to record state. + +use core::marker; + +use {Rng, Rand}; + +pub use self::range::Range; +#[cfg(feature="std")] +pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT}; +#[cfg(feature="std")] +pub use self::normal::{Normal, LogNormal}; +#[cfg(feature="std")] +pub use self::exponential::Exp; + +pub mod range; +#[cfg(feature="std")] +pub mod gamma; +#[cfg(feature="std")] +pub mod normal; +#[cfg(feature="std")] +pub mod exponential; + +#[cfg(feature="std")] +mod ziggurat_tables; + +/// Types that can be used to create a random instance of `Support`. +pub trait Sample<Support> { + /// Generate a random value of `Support`, using `rng` as the + /// source of randomness. + fn sample<R: Rng>(&mut self, rng: &mut R) -> Support; +} + +/// `Sample`s that do not require keeping track of state. +/// +/// Since no state is recorded, each sample is (statistically) +/// independent of all others, assuming the `Rng` used has this +/// property. +// FIXME maybe having this separate is overkill (the only reason is to +// take &self rather than &mut self)? or maybe this should be the +// trait called `Sample` and the other should be `DependentSample`. +pub trait IndependentSample<Support>: Sample<Support> { + /// Generate a random value. + fn ind_sample<R: Rng>(&self, &mut R) -> Support; +} + +/// A wrapper for generating types that implement `Rand` via the +/// `Sample` & `IndependentSample` traits. +#[derive(Debug)] +pub struct RandSample<Sup> { + _marker: marker::PhantomData<fn() -> Sup>, +} + +impl<Sup> Copy for RandSample<Sup> {} +impl<Sup> Clone for RandSample<Sup> { + fn clone(&self) -> Self { *self } +} + +impl<Sup: Rand> Sample<Sup> for RandSample<Sup> { + fn sample<R: Rng>(&mut self, rng: &mut R) -> Sup { self.ind_sample(rng) } +} + +impl<Sup: Rand> IndependentSample<Sup> for RandSample<Sup> { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> Sup { + rng.gen() + } +} + +impl<Sup> RandSample<Sup> { + pub fn new() -> RandSample<Sup> { + RandSample { _marker: marker::PhantomData } + } +} + +/// A value with a particular weight for use with `WeightedChoice`. +#[derive(Copy, Clone, Debug)] +pub struct Weighted<T> { + /// The numerical weight of this item + pub weight: u32, + /// The actual item which is being weighted + pub item: T, +} + +/// A distribution that selects from a finite collection of weighted items. +/// +/// Each item has an associated weight that influences how likely it +/// is to be chosen: higher weight is more likely. +/// +/// The `Clone` restriction is a limitation of the `Sample` and +/// `IndependentSample` traits. Note that `&T` is (cheaply) `Clone` for +/// all `T`, as is `u32`, so one can store references or indices into +/// another vector. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{Weighted, WeightedChoice, IndependentSample}; +/// +/// let mut items = vec!(Weighted { weight: 2, item: 'a' }, +/// Weighted { weight: 4, item: 'b' }, +/// Weighted { weight: 1, item: 'c' }); +/// let wc = WeightedChoice::new(&mut items); +/// let mut rng = rand::thread_rng(); +/// for _ in 0..16 { +/// // on average prints 'a' 4 times, 'b' 8 and 'c' twice. +/// println!("{}", wc.ind_sample(&mut rng)); +/// } +/// ``` +#[derive(Debug)] +pub struct WeightedChoice<'a, T:'a> { + items: &'a mut [Weighted<T>], + weight_range: Range<u32> +} + +impl<'a, T: Clone> WeightedChoice<'a, T> { + /// Create a new `WeightedChoice`. + /// + /// Panics if: + /// + /// - `items` is empty + /// - the total weight is 0 + /// - the total weight is larger than a `u32` can contain. + pub fn new(items: &'a mut [Weighted<T>]) -> WeightedChoice<'a, T> { + // strictly speaking, this is subsumed by the total weight == 0 case + assert!(!items.is_empty(), "WeightedChoice::new called with no items"); + + let mut running_total: u32 = 0; + + // we convert the list from individual weights to cumulative + // weights so we can binary search. This *could* drop elements + // with weight == 0 as an optimisation. + for item in items.iter_mut() { + running_total = match running_total.checked_add(item.weight) { + Some(n) => n, + None => panic!("WeightedChoice::new called with a total weight \ + larger than a u32 can contain") + }; + + item.weight = running_total; + } + assert!(running_total != 0, "WeightedChoice::new called with a total weight of 0"); + + WeightedChoice { + items: items, + // we're likely to be generating numbers in this range + // relatively often, so might as well cache it + weight_range: Range::new(0, running_total) + } + } +} + +impl<'a, T: Clone> Sample<T> for WeightedChoice<'a, T> { + fn sample<R: Rng>(&mut self, rng: &mut R) -> T { self.ind_sample(rng) } +} + +impl<'a, T: Clone> IndependentSample<T> for WeightedChoice<'a, T> { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> T { + // we want to find the first element that has cumulative + // weight > sample_weight, which we do by binary since the + // cumulative weights of self.items are sorted. + + // choose a weight in [0, total_weight) + let sample_weight = self.weight_range.ind_sample(rng); + + // short circuit when it's the first item + if sample_weight < self.items[0].weight { + return self.items[0].item.clone(); + } + + let mut idx = 0; + let mut modifier = self.items.len(); + + // now we know that every possibility has an element to the + // left, so we can just search for the last element that has + // cumulative weight <= sample_weight, then the next one will + // be "it". (Note that this greatest element will never be the + // last element of the vector, since sample_weight is chosen + // in [0, total_weight) and the cumulative weight of the last + // one is exactly the total weight.) + while modifier > 1 { + let i = idx + modifier / 2; + if self.items[i].weight <= sample_weight { + // we're small, so look to the right, but allow this + // exact element still. + idx = i; + // we need the `/ 2` to round up otherwise we'll drop + // the trailing elements when `modifier` is odd. + modifier += 1; + } else { + // otherwise we're too big, so go left. (i.e. do + // nothing) + } + modifier /= 2; + } + return self.items[idx + 1].item.clone(); + } +} + +/// Sample a random number using the Ziggurat method (specifically the +/// ZIGNOR variant from Doornik 2005). Most of the arguments are +/// directly from the paper: +/// +/// * `rng`: source of randomness +/// * `symmetric`: whether this is a symmetric distribution, or one-sided with P(x < 0) = 0. +/// * `X`: the $x_i$ abscissae. +/// * `F`: precomputed values of the PDF at the $x_i$, (i.e. $f(x_i)$) +/// * `F_DIFF`: precomputed values of $f(x_i) - f(x_{i+1})$ +/// * `pdf`: the probability density function +/// * `zero_case`: manual sampling from the tail when we chose the +/// bottom box (i.e. i == 0) + +// the perf improvement (25-50%) is definitely worth the extra code +// size from force-inlining. +#[cfg(feature="std")] +#[inline(always)] +fn ziggurat<R: Rng, P, Z>( + rng: &mut R, + symmetric: bool, + x_tab: ziggurat_tables::ZigTable, + f_tab: ziggurat_tables::ZigTable, + mut pdf: P, + mut zero_case: Z) + -> f64 where P: FnMut(f64) -> f64, Z: FnMut(&mut R, f64) -> f64 { + const SCALE: f64 = (1u64 << 53) as f64; + loop { + // reimplement the f64 generation as an optimisation suggested + // by the Doornik paper: we have a lot of precision-space + // (i.e. there are 11 bits of the 64 of a u64 to use after + // creating a f64), so we might as well reuse some to save + // generating a whole extra random number. (Seems to be 15% + // faster.) + // + // This unfortunately misses out on the benefits of direct + // floating point generation if an RNG like dSMFT is + // used. (That is, such RNGs create floats directly, highly + // efficiently and overload next_f32/f64, so by not calling it + // this may be slower than it would be otherwise.) + // FIXME: investigate/optimise for the above. + let bits: u64 = rng.gen(); + let i = (bits & 0xff) as usize; + let f = (bits >> 11) as f64 / SCALE; + + // u is either U(-1, 1) or U(0, 1) depending on if this is a + // symmetric distribution or not. + let u = if symmetric {2.0 * f - 1.0} else {f}; + let x = u * x_tab[i]; + + let test_x = if symmetric { x.abs() } else {x}; + + // algebraically equivalent to |u| < x_tab[i+1]/x_tab[i] (or u < x_tab[i+1]/x_tab[i]) + if test_x < x_tab[i + 1] { + return x; + } + if i == 0 { + return zero_case(rng, u); + } + // algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1 + if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::<f64>() < pdf(x) { + return x; + } + } +} + +#[cfg(test)] +mod tests { + + use {Rng, Rand}; + use super::{RandSample, WeightedChoice, Weighted, Sample, IndependentSample}; + + #[derive(PartialEq, Debug)] + struct ConstRand(usize); + impl Rand for ConstRand { + fn rand<R: Rng>(_: &mut R) -> ConstRand { + ConstRand(0) + } + } + + // 0, 1, 2, 3, ... + struct CountingRng { i: u32 } + impl Rng for CountingRng { + fn next_u32(&mut self) -> u32 { + self.i += 1; + self.i - 1 + } + fn next_u64(&mut self) -> u64 { + self.next_u32() as u64 + } + } + + #[test] + fn test_rand_sample() { + let mut rand_sample = RandSample::<ConstRand>::new(); + + assert_eq!(rand_sample.sample(&mut ::test::rng()), ConstRand(0)); + assert_eq!(rand_sample.ind_sample(&mut ::test::rng()), ConstRand(0)); + } + #[test] + fn test_weighted_choice() { + // this makes assumptions about the internal implementation of + // WeightedChoice, specifically: it doesn't reorder the items, + // it doesn't do weird things to the RNG (so 0 maps to 0, 1 to + // 1, internally; modulo a modulo operation). + + macro_rules! t { + ($items:expr, $expected:expr) => {{ + let mut items = $items; + let wc = WeightedChoice::new(&mut items); + let expected = $expected; + + let mut rng = CountingRng { i: 0 }; + + for &val in expected.iter() { + assert_eq!(wc.ind_sample(&mut rng), val) + } + }} + } + + t!(vec!(Weighted { weight: 1, item: 10}), [10]); + + // skip some + t!(vec!(Weighted { weight: 0, item: 20}, + Weighted { weight: 2, item: 21}, + Weighted { weight: 0, item: 22}, + Weighted { weight: 1, item: 23}), + [21,21, 23]); + + // different weights + t!(vec!(Weighted { weight: 4, item: 30}, + Weighted { weight: 3, item: 31}), + [30,30,30,30, 31,31,31]); + + // check that we're binary searching + // correctly with some vectors of odd + // length. + t!(vec!(Weighted { weight: 1, item: 40}, + Weighted { weight: 1, item: 41}, + Weighted { weight: 1, item: 42}, + Weighted { weight: 1, item: 43}, + Weighted { weight: 1, item: 44}), + [40, 41, 42, 43, 44]); + t!(vec!(Weighted { weight: 1, item: 50}, + Weighted { weight: 1, item: 51}, + Weighted { weight: 1, item: 52}, + Weighted { weight: 1, item: 53}, + Weighted { weight: 1, item: 54}, + Weighted { weight: 1, item: 55}, + Weighted { weight: 1, item: 56}), + [50, 51, 52, 53, 54, 55, 56]); + } + + #[test] + fn test_weighted_clone_initialization() { + let initial : Weighted<u32> = Weighted {weight: 1, item: 1}; + let clone = initial.clone(); + assert_eq!(initial.weight, clone.weight); + assert_eq!(initial.item, clone.item); + } + + #[test] #[should_panic] + fn test_weighted_clone_change_weight() { + let initial : Weighted<u32> = Weighted {weight: 1, item: 1}; + let mut clone = initial.clone(); + clone.weight = 5; + assert_eq!(initial.weight, clone.weight); + } + + #[test] #[should_panic] + fn test_weighted_clone_change_item() { + let initial : Weighted<u32> = Weighted {weight: 1, item: 1}; + let mut clone = initial.clone(); + clone.item = 5; + assert_eq!(initial.item, clone.item); + + } + + #[test] #[should_panic] + fn test_weighted_choice_no_items() { + WeightedChoice::<isize>::new(&mut []); + } + #[test] #[should_panic] + fn test_weighted_choice_zero_weight() { + WeightedChoice::new(&mut [Weighted { weight: 0, item: 0}, + Weighted { weight: 0, item: 1}]); + } + #[test] #[should_panic] + fn test_weighted_choice_weight_overflows() { + let x = ::std::u32::MAX / 2; // x + x + 2 is the overflow + WeightedChoice::new(&mut [Weighted { weight: x, item: 0 }, + Weighted { weight: 1, item: 1 }, + Weighted { weight: x, item: 2 }, + Weighted { weight: 1, item: 3 }]); + } +} diff --git a/rand/src/distributions/normal.rs b/rand/src/distributions/normal.rs new file mode 100644 index 0000000..280613d --- /dev/null +++ b/rand/src/distributions/normal.rs @@ -0,0 +1,201 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! The normal and derived distributions. + +use {Rng, Rand, Open01}; +use distributions::{ziggurat, ziggurat_tables, Sample, IndependentSample}; + +/// A wrapper around an `f64` to generate N(0, 1) random numbers +/// (a.k.a. a standard normal, or Gaussian). +/// +/// See `Normal` for the general normal distribution. +/// +/// Implemented via the ZIGNOR variant[1] of the Ziggurat method. +/// +/// [1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to +/// Generate Normal Random +/// Samples*](http://www.doornik.com/research/ziggurat.pdf). Nuffield +/// College, Oxford +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::normal::StandardNormal; +/// +/// let StandardNormal(x) = rand::random(); +/// println!("{}", x); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct StandardNormal(pub f64); + +impl Rand for StandardNormal { + fn rand<R:Rng>(rng: &mut R) -> StandardNormal { + #[inline] + fn pdf(x: f64) -> f64 { + (-x*x/2.0).exp() + } + #[inline] + fn zero_case<R:Rng>(rng: &mut R, u: f64) -> f64 { + // compute a random number in the tail by hand + + // strange initial conditions, because the loop is not + // do-while, so the condition should be true on the first + // run, they get overwritten anyway (0 < 1, so these are + // good). + let mut x = 1.0f64; + let mut y = 0.0f64; + + while -2.0 * y < x * x { + let Open01(x_) = rng.gen::<Open01<f64>>(); + let Open01(y_) = rng.gen::<Open01<f64>>(); + + x = x_.ln() / ziggurat_tables::ZIG_NORM_R; + y = y_.ln(); + } + + if u < 0.0 { x - ziggurat_tables::ZIG_NORM_R } else { ziggurat_tables::ZIG_NORM_R - x } + } + + StandardNormal(ziggurat( + rng, + true, // this is symmetric + &ziggurat_tables::ZIG_NORM_X, + &ziggurat_tables::ZIG_NORM_F, + pdf, zero_case)) + } +} + +/// The normal distribution `N(mean, std_dev**2)`. +/// +/// This uses the ZIGNOR variant of the Ziggurat method, see +/// `StandardNormal` for more details. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{Normal, IndependentSample}; +/// +/// // mean 2, standard deviation 3 +/// let normal = Normal::new(2.0, 3.0); +/// let v = normal.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from a N(2, 9) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Normal { + mean: f64, + std_dev: f64, +} + +impl Normal { + /// Construct a new `Normal` distribution with the given mean and + /// standard deviation. + /// + /// # Panics + /// + /// Panics if `std_dev < 0`. + #[inline] + pub fn new(mean: f64, std_dev: f64) -> Normal { + assert!(std_dev >= 0.0, "Normal::new called with `std_dev` < 0"); + Normal { + mean: mean, + std_dev: std_dev + } + } +} +impl Sample<f64> for Normal { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl IndependentSample<f64> for Normal { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + let StandardNormal(n) = rng.gen::<StandardNormal>(); + self.mean + self.std_dev * n + } +} + + +/// The log-normal distribution `ln N(mean, std_dev**2)`. +/// +/// If `X` is log-normal distributed, then `ln(X)` is `N(mean, +/// std_dev**2)` distributed. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{LogNormal, IndependentSample}; +/// +/// // mean 2, standard deviation 3 +/// let log_normal = LogNormal::new(2.0, 3.0); +/// let v = log_normal.ind_sample(&mut rand::thread_rng()); +/// println!("{} is from an ln N(2, 9) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct LogNormal { + norm: Normal +} + +impl LogNormal { + /// Construct a new `LogNormal` distribution with the given mean + /// and standard deviation. + /// + /// # Panics + /// + /// Panics if `std_dev < 0`. + #[inline] + pub fn new(mean: f64, std_dev: f64) -> LogNormal { + assert!(std_dev >= 0.0, "LogNormal::new called with `std_dev` < 0"); + LogNormal { norm: Normal::new(mean, std_dev) } + } +} +impl Sample<f64> for LogNormal { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl IndependentSample<f64> for LogNormal { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + self.norm.ind_sample(rng).exp() + } +} + +#[cfg(test)] +mod tests { + use distributions::{Sample, IndependentSample}; + use super::{Normal, LogNormal}; + + #[test] + fn test_normal() { + let mut norm = Normal::new(10.0, 10.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + norm.sample(&mut rng); + norm.ind_sample(&mut rng); + } + } + #[test] + #[should_panic] + fn test_normal_invalid_sd() { + Normal::new(10.0, -1.0); + } + + + #[test] + fn test_log_normal() { + let mut lnorm = LogNormal::new(10.0, 10.0); + let mut rng = ::test::rng(); + for _ in 0..1000 { + lnorm.sample(&mut rng); + lnorm.ind_sample(&mut rng); + } + } + #[test] + #[should_panic] + fn test_log_normal_invalid_sd() { + LogNormal::new(10.0, -1.0); + } +} diff --git a/rand/src/distributions/range.rs b/rand/src/distributions/range.rs new file mode 100644 index 0000000..935a00a --- /dev/null +++ b/rand/src/distributions/range.rs @@ -0,0 +1,241 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Generating numbers between two others. + +// this is surprisingly complicated to be both generic & correct + +use core::num::Wrapping as w; + +use Rng; +use distributions::{Sample, IndependentSample}; + +/// Sample values uniformly between two bounds. +/// +/// This gives a uniform distribution (assuming the RNG used to sample +/// it is itself uniform & the `SampleRange` implementation for the +/// given type is correct), even for edge cases like `low = 0u8`, +/// `high = 170u8`, for which a naive modulo operation would return +/// numbers less than 85 with double the probability to those greater +/// than 85. +/// +/// Types should attempt to sample in `[low, high)`, i.e., not +/// including `high`, but this may be very difficult. All the +/// primitive integer types satisfy this property, and the float types +/// normally satisfy it, but rounding may mean `high` can occur. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{IndependentSample, Range}; +/// +/// fn main() { +/// let between = Range::new(10, 10000); +/// let mut rng = rand::thread_rng(); +/// let mut sum = 0; +/// for _ in 0..1000 { +/// sum += between.ind_sample(&mut rng); +/// } +/// println!("{}", sum); +/// } +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Range<X> { + low: X, + range: X, + accept_zone: X +} + +impl<X: SampleRange + PartialOrd> Range<X> { + /// Create a new `Range` instance that samples uniformly from + /// `[low, high)`. Panics if `low >= high`. + pub fn new(low: X, high: X) -> Range<X> { + assert!(low < high, "Range::new called with `low >= high`"); + SampleRange::construct_range(low, high) + } +} + +impl<Sup: SampleRange> Sample<Sup> for Range<Sup> { + #[inline] + fn sample<R: Rng>(&mut self, rng: &mut R) -> Sup { self.ind_sample(rng) } +} +impl<Sup: SampleRange> IndependentSample<Sup> for Range<Sup> { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> Sup { + SampleRange::sample_range(self, rng) + } +} + +/// The helper trait for types that have a sensible way to sample +/// uniformly between two values. This should not be used directly, +/// and is only to facilitate `Range`. +pub trait SampleRange : Sized { + /// Construct the `Range` object that `sample_range` + /// requires. This should not ever be called directly, only via + /// `Range::new`, which will check that `low < high`, so this + /// function doesn't have to repeat the check. + fn construct_range(low: Self, high: Self) -> Range<Self>; + + /// Sample a value from the given `Range` with the given `Rng` as + /// a source of randomness. + fn sample_range<R: Rng>(r: &Range<Self>, rng: &mut R) -> Self; +} + +macro_rules! integer_impl { + ($ty:ty, $unsigned:ident) => { + impl SampleRange for $ty { + // we play free and fast with unsigned vs signed here + // (when $ty is signed), but that's fine, since the + // contract of this macro is for $ty and $unsigned to be + // "bit-equal", so casting between them is a no-op & a + // bijection. + + #[inline] + fn construct_range(low: $ty, high: $ty) -> Range<$ty> { + let range = (w(high as $unsigned) - w(low as $unsigned)).0; + let unsigned_max: $unsigned = ::core::$unsigned::MAX; + + // this is the largest number that fits into $unsigned + // that `range` divides evenly, so, if we've sampled + // `n` uniformly from this region, then `n % range` is + // uniform in [0, range) + let zone = unsigned_max - unsigned_max % range; + + Range { + low: low, + range: range as $ty, + accept_zone: zone as $ty + } + } + + #[inline] + fn sample_range<R: Rng>(r: &Range<$ty>, rng: &mut R) -> $ty { + loop { + // rejection sample + let v = rng.gen::<$unsigned>(); + // until we find something that fits into the + // region which r.range evenly divides (this will + // be uniformly distributed) + if v < r.accept_zone as $unsigned { + // and return it, with some adjustments + return (w(r.low) + w((v % r.range as $unsigned) as $ty)).0; + } + } + } + } + } +} + +integer_impl! { i8, u8 } +integer_impl! { i16, u16 } +integer_impl! { i32, u32 } +integer_impl! { i64, u64 } +#[cfg(feature = "i128_support")] +integer_impl! { i128, u128 } +integer_impl! { isize, usize } +integer_impl! { u8, u8 } +integer_impl! { u16, u16 } +integer_impl! { u32, u32 } +integer_impl! { u64, u64 } +#[cfg(feature = "i128_support")] +integer_impl! { u128, u128 } +integer_impl! { usize, usize } + +macro_rules! float_impl { + ($ty:ty) => { + impl SampleRange for $ty { + fn construct_range(low: $ty, high: $ty) -> Range<$ty> { + Range { + low: low, + range: high - low, + accept_zone: 0.0 // unused + } + } + fn sample_range<R: Rng>(r: &Range<$ty>, rng: &mut R) -> $ty { + r.low + r.range * rng.gen::<$ty>() + } + } + } +} + +float_impl! { f32 } +float_impl! { f64 } + +#[cfg(test)] +mod tests { + use distributions::{Sample, IndependentSample}; + use super::Range as Range; + + #[should_panic] + #[test] + fn test_range_bad_limits_equal() { + Range::new(10, 10); + } + #[should_panic] + #[test] + fn test_range_bad_limits_flipped() { + Range::new(10, 5); + } + + #[test] + fn test_integers() { + let mut rng = ::test::rng(); + macro_rules! t { + ($($ty:ident),*) => {{ + $( + let v: &[($ty, $ty)] = &[(0, 10), + (10, 127), + (::core::$ty::MIN, ::core::$ty::MAX)]; + for &(low, high) in v.iter() { + let mut sampler: Range<$ty> = Range::new(low, high); + for _ in 0..1000 { + let v = sampler.sample(&mut rng); + assert!(low <= v && v < high); + let v = sampler.ind_sample(&mut rng); + assert!(low <= v && v < high); + } + } + )* + }} + } + #[cfg(not(feature = "i128_support"))] + t!(i8, i16, i32, i64, isize, + u8, u16, u32, u64, usize); + #[cfg(feature = "i128_support")] + t!(i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize); + } + + #[test] + fn test_floats() { + let mut rng = ::test::rng(); + macro_rules! t { + ($($ty:ty),*) => {{ + $( + let v: &[($ty, $ty)] = &[(0.0, 100.0), + (-1e35, -1e25), + (1e-35, 1e-25), + (-1e35, 1e35)]; + for &(low, high) in v.iter() { + let mut sampler: Range<$ty> = Range::new(low, high); + for _ in 0..1000 { + let v = sampler.sample(&mut rng); + assert!(low <= v && v < high); + let v = sampler.ind_sample(&mut rng); + assert!(low <= v && v < high); + } + } + )* + }} + } + + t!(f32, f64) + } + +} diff --git a/rand/src/distributions/ziggurat_tables.rs b/rand/src/distributions/ziggurat_tables.rs new file mode 100644 index 0000000..b6de4bf --- /dev/null +++ b/rand/src/distributions/ziggurat_tables.rs @@ -0,0 +1,280 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +// Tables for distributions which are sampled using the ziggurat +// algorithm. Autogenerated by `ziggurat_tables.py`. + +pub type ZigTable = &'static [f64; 257]; +pub const ZIG_NORM_R: f64 = 3.654152885361008796; +pub static ZIG_NORM_X: [f64; 257] = + [3.910757959537090045, 3.654152885361008796, 3.449278298560964462, 3.320244733839166074, + 3.224575052047029100, 3.147889289517149969, 3.083526132001233044, 3.027837791768635434, + 2.978603279880844834, 2.934366867207854224, 2.894121053612348060, 2.857138730872132548, + 2.822877396825325125, 2.790921174000785765, 2.760944005278822555, 2.732685359042827056, + 2.705933656121858100, 2.680514643284522158, 2.656283037575502437, 2.633116393630324570, + 2.610910518487548515, 2.589575986706995181, 2.569035452680536569, 2.549221550323460761, + 2.530075232158516929, 2.511544441625342294, 2.493583041269680667, 2.476149939669143318, + 2.459208374333311298, 2.442725318198956774, 2.426670984935725972, 2.411018413899685520, + 2.395743119780480601, 2.380822795170626005, 2.366237056715818632, 2.351967227377659952, + 2.337996148795031370, 2.324308018869623016, 2.310888250599850036, 2.297723348901329565, + 2.284800802722946056, 2.272108990226823888, 2.259637095172217780, 2.247375032945807760, + 2.235313384928327984, 2.223443340090905718, 2.211756642882544366, 2.200245546609647995, + 2.188902771624720689, 2.177721467738641614, 2.166695180352645966, 2.155817819875063268, + 2.145083634046203613, 2.134487182844320152, 2.124023315687815661, 2.113687150684933957, + 2.103474055713146829, 2.093379631137050279, 2.083399693996551783, 2.073530263516978778, + 2.063767547809956415, 2.054107931648864849, 2.044547965215732788, 2.035084353727808715, + 2.025713947862032960, 2.016433734904371722, 2.007240830558684852, 1.998132471356564244, + 1.989106007615571325, 1.980158896898598364, 1.971288697931769640, 1.962493064942461896, + 1.953769742382734043, 1.945116560006753925, 1.936531428273758904, 1.928012334050718257, + 1.919557336591228847, 1.911164563769282232, 1.902832208548446369, 1.894558525668710081, + 1.886341828534776388, 1.878180486290977669, 1.870072921069236838, 1.862017605397632281, + 1.854013059758148119, 1.846057850283119750, 1.838150586580728607, 1.830289919680666566, + 1.822474540091783224, 1.814703175964167636, 1.806974591348693426, 1.799287584547580199, + 1.791640986550010028, 1.784033659547276329, 1.776464495522344977, 1.768932414909077933, + 1.761436365316706665, 1.753975320315455111, 1.746548278279492994, 1.739154261283669012, + 1.731792314050707216, 1.724461502945775715, 1.717160915015540690, 1.709889657069006086, + 1.702646854797613907, 1.695431651932238548, 1.688243209434858727, 1.681080704722823338, + 1.673943330923760353, 1.666830296159286684, 1.659740822855789499, 1.652674147080648526, + 1.645629517902360339, 1.638606196773111146, 1.631603456932422036, 1.624620582830568427, + 1.617656869570534228, 1.610711622367333673, 1.603784156023583041, 1.596873794420261339, + 1.589979870021648534, 1.583101723393471438, 1.576238702733332886, 1.569390163412534456, + 1.562555467528439657, 1.555733983466554893, 1.548925085471535512, 1.542128153226347553, + 1.535342571438843118, 1.528567729435024614, 1.521803020758293101, 1.515047842773992404, + 1.508301596278571965, 1.501563685112706548, 1.494833515777718391, 1.488110497054654369, + 1.481394039625375747, 1.474683555695025516, 1.467978458615230908, 1.461278162507407830, + 1.454582081885523293, 1.447889631277669675, 1.441200224845798017, 1.434513276002946425, + 1.427828197027290358, 1.421144398672323117, 1.414461289772464658, 1.407778276843371534, + 1.401094763676202559, 1.394410150925071257, 1.387723835686884621, 1.381035211072741964, + 1.374343665770030531, 1.367648583594317957, 1.360949343030101844, 1.354245316759430606, + 1.347535871177359290, 1.340820365893152122, 1.334098153216083604, 1.327368577624624679, + 1.320630975217730096, 1.313884673146868964, 1.307128989027353860, 1.300363230327433728, + 1.293586693733517645, 1.286798664489786415, 1.279998415710333237, 1.273185207661843732, + 1.266358287014688333, 1.259516886060144225, 1.252660221891297887, 1.245787495544997903, + 1.238897891102027415, 1.231990574742445110, 1.225064693752808020, 1.218119375481726552, + 1.211153726239911244, 1.204166830140560140, 1.197157747875585931, 1.190125515422801650, + 1.183069142678760732, 1.175987612011489825, 1.168879876726833800, 1.161744859441574240, + 1.154581450355851802, 1.147388505416733873, 1.140164844363995789, 1.132909248648336975, + 1.125620459211294389, 1.118297174115062909, 1.110938046009249502, 1.103541679420268151, + 1.096106627847603487, 1.088631390649514197, 1.081114409698889389, 1.073554065787871714, + 1.065948674757506653, 1.058296483326006454, 1.050595664586207123, 1.042844313139370538, + 1.035040439828605274, 1.027181966030751292, 1.019266717460529215, 1.011292417434978441, + 1.003256679539591412, 0.995156999629943084, 0.986990747093846266, 0.978755155288937750, + 0.970447311058864615, 0.962064143217605250, 0.953602409875572654, 0.945058684462571130, + 0.936429340280896860, 0.927710533396234771, 0.918898183643734989, 0.909987953490768997, + 0.900975224455174528, 0.891855070726792376, 0.882622229578910122, 0.873271068082494550, + 0.863795545546826915, 0.854189171001560554, 0.844444954902423661, 0.834555354079518752, + 0.824512208745288633, 0.814306670128064347, 0.803929116982664893, 0.793369058833152785, + 0.782615023299588763, 0.771654424216739354, 0.760473406422083165, 0.749056662009581653, + 0.737387211425838629, 0.725446140901303549, 0.713212285182022732, 0.700661841097584448, + 0.687767892786257717, 0.674499822827436479, 0.660822574234205984, 0.646695714884388928, + 0.632072236375024632, 0.616896989996235545, 0.601104617743940417, 0.584616766093722262, + 0.567338257040473026, 0.549151702313026790, 0.529909720646495108, 0.509423329585933393, + 0.487443966121754335, 0.463634336771763245, 0.437518402186662658, 0.408389134588000746, + 0.375121332850465727, 0.335737519180459465, 0.286174591747260509, 0.215241895913273806, + 0.000000000000000000]; +pub static ZIG_NORM_F: [f64; 257] = + [0.000477467764586655, 0.001260285930498598, 0.002609072746106363, 0.004037972593371872, + 0.005522403299264754, 0.007050875471392110, 0.008616582769422917, 0.010214971439731100, + 0.011842757857943104, 0.013497450601780807, 0.015177088307982072, 0.016880083152595839, + 0.018605121275783350, 0.020351096230109354, 0.022117062707379922, 0.023902203305873237, + 0.025705804008632656, 0.027527235669693315, 0.029365939758230111, 0.031221417192023690, + 0.033093219458688698, 0.034980941461833073, 0.036884215688691151, 0.038802707404656918, + 0.040736110656078753, 0.042684144916619378, 0.044646552251446536, 0.046623094902089664, + 0.048613553216035145, 0.050617723861121788, 0.052635418276973649, 0.054666461325077916, + 0.056710690106399467, 0.058767952921137984, 0.060838108349751806, 0.062921024437977854, + 0.065016577971470438, 0.067124653828023989, 0.069245144397250269, 0.071377949059141965, + 0.073522973714240991, 0.075680130359194964, 0.077849336702372207, 0.080030515814947509, + 0.082223595813495684, 0.084428509570654661, 0.086645194450867782, 0.088873592068594229, + 0.091113648066700734, 0.093365311913026619, 0.095628536713353335, 0.097903279039215627, + 0.100189498769172020, 0.102487158942306270, 0.104796225622867056, 0.107116667775072880, + 0.109448457147210021, 0.111791568164245583, 0.114145977828255210, 0.116511665626037014, + 0.118888613443345698, 0.121276805485235437, 0.123676228202051403, 0.126086870220650349, + 0.128508722280473636, 0.130941777174128166, 0.133386029692162844, 0.135841476571757352, + 0.138308116449064322, 0.140785949814968309, 0.143274978974047118, 0.145775208006537926, + 0.148286642733128721, 0.150809290682410169, 0.153343161060837674, 0.155888264725064563, + 0.158444614156520225, 0.161012223438117663, 0.163591108232982951, 0.166181285765110071, + 0.168782774801850333, 0.171395595638155623, 0.174019770082499359, 0.176655321444406654, + 0.179302274523530397, 0.181960655600216487, 0.184630492427504539, 0.187311814224516926, + 0.190004651671193070, 0.192709036904328807, 0.195425003514885592, 0.198152586546538112, + 0.200891822495431333, 0.203642749311121501, 0.206405406398679298, 0.209179834621935651, + 0.211966076307852941, 0.214764175252008499, 0.217574176725178370, 0.220396127481011589, + 0.223230075764789593, 0.226076071323264877, 0.228934165415577484, 0.231804410825248525, + 0.234686861873252689, 0.237581574432173676, 0.240488605941449107, 0.243408015423711988, + 0.246339863502238771, 0.249284212419516704, 0.252241126056943765, 0.255210669955677150, + 0.258192911338648023, 0.261187919133763713, 0.264195763998317568, 0.267216518344631837, + 0.270250256366959984, 0.273297054069675804, 0.276356989296781264, 0.279430141762765316, + 0.282516593084849388, 0.285616426816658109, 0.288729728483353931, 0.291856585618280984, + 0.294997087801162572, 0.298151326697901342, 0.301319396102034120, 0.304501391977896274, + 0.307697412505553769, 0.310907558127563710, 0.314131931597630143, 0.317370638031222396, + 0.320623784958230129, 0.323891482377732021, 0.327173842814958593, 0.330470981380537099, + 0.333783015832108509, 0.337110066638412809, 0.340452257045945450, 0.343809713148291340, + 0.347182563958251478, 0.350570941482881204, 0.353974980801569250, 0.357394820147290515, + 0.360830600991175754, 0.364282468130549597, 0.367750569780596226, 0.371235057669821344, + 0.374736087139491414, 0.378253817247238111, 0.381788410875031348, 0.385340034841733958, + 0.388908860020464597, 0.392495061461010764, 0.396098818517547080, 0.399720314981931668, + 0.403359739222868885, 0.407017284331247953, 0.410693148271983222, 0.414387534042706784, + 0.418100649839684591, 0.421832709231353298, 0.425583931339900579, 0.429354541031341519, + 0.433144769114574058, 0.436954852549929273, 0.440785034667769915, 0.444635565397727750, + 0.448506701509214067, 0.452398706863882505, 0.456311852680773566, 0.460246417814923481, + 0.464202689050278838, 0.468180961407822172, 0.472181538469883255, 0.476204732721683788, + 0.480250865911249714, 0.484320269428911598, 0.488413284707712059, 0.492530263646148658, + 0.496671569054796314, 0.500837575128482149, 0.505028667945828791, 0.509245245998136142, + 0.513487720749743026, 0.517756517232200619, 0.522052074674794864, 0.526374847174186700, + 0.530725304406193921, 0.535103932383019565, 0.539511234259544614, 0.543947731192649941, + 0.548413963257921133, 0.552910490428519918, 0.557437893621486324, 0.561996775817277916, + 0.566587763258951771, 0.571211506738074970, 0.575868682975210544, 0.580559996103683473, + 0.585286179266300333, 0.590047996335791969, 0.594846243770991268, 0.599681752622167719, + 0.604555390700549533, 0.609468064928895381, 0.614420723892076803, 0.619414360609039205, + 0.624450015550274240, 0.629528779928128279, 0.634651799290960050, 0.639820277456438991, + 0.645035480824251883, 0.650298743114294586, 0.655611470583224665, 0.660975147780241357, + 0.666391343912380640, 0.671861719900766374, 0.677388036222513090, 0.682972161648791376, + 0.688616083008527058, 0.694321916130032579, 0.700091918140490099, 0.705928501336797409, + 0.711834248882358467, 0.717811932634901395, 0.723864533472881599, 0.729995264565802437, + 0.736207598131266683, 0.742505296344636245, 0.748892447223726720, 0.755373506511754500, + 0.761953346841546475, 0.768637315803334831, 0.775431304986138326, 0.782341832659861902, + 0.789376143571198563, 0.796542330428254619, 0.803849483176389490, 0.811307874318219935, + 0.818929191609414797, 0.826726833952094231, 0.834716292992930375, 0.842915653118441077, + 0.851346258465123684, 0.860033621203008636, 0.869008688043793165, 0.878309655816146839, + 0.887984660763399880, 0.898095921906304051, 0.908726440060562912, 0.919991505048360247, + 0.932060075968990209, 0.945198953453078028, 0.959879091812415930, 0.977101701282731328, + 1.000000000000000000]; +pub const ZIG_EXP_R: f64 = 7.697117470131050077; +pub static ZIG_EXP_X: [f64; 257] = + [8.697117470131052741, 7.697117470131050077, 6.941033629377212577, 6.478378493832569696, + 6.144164665772472667, 5.882144315795399869, 5.666410167454033697, 5.482890627526062488, + 5.323090505754398016, 5.181487281301500047, 5.054288489981304089, 4.938777085901250530, + 4.832939741025112035, 4.735242996601741083, 4.644491885420085175, 4.559737061707351380, + 4.480211746528421912, 4.405287693473573185, 4.334443680317273007, 4.267242480277365857, + 4.203313713735184365, 4.142340865664051464, 4.084051310408297830, 4.028208544647936762, + 3.974606066673788796, 3.923062500135489739, 3.873417670399509127, 3.825529418522336744, + 3.779270992411667862, 3.734528894039797375, 3.691201090237418825, 3.649195515760853770, + 3.608428813128909507, 3.568825265648337020, 3.530315889129343354, 3.492837654774059608, + 3.456332821132760191, 3.420748357251119920, 3.386035442460300970, 3.352149030900109405, + 3.319047470970748037, 3.286692171599068679, 3.255047308570449882, 3.224079565286264160, + 3.193757903212240290, 3.164053358025972873, 3.134938858084440394, 3.106389062339824481, + 3.078380215254090224, 3.050890016615455114, 3.023897504455676621, 2.997382949516130601, + 2.971327759921089662, 2.945714394895045718, 2.920526286512740821, 2.895747768600141825, + 2.871364012015536371, 2.847360965635188812, 2.823725302450035279, 2.800444370250737780, + 2.777506146439756574, 2.754899196562344610, 2.732612636194700073, 2.710636095867928752, + 2.688959688741803689, 2.667573980773266573, 2.646469963151809157, 2.625639026797788489, + 2.605072938740835564, 2.584763820214140750, 2.564704126316905253, 2.544886627111869970, + 2.525304390037828028, 2.505950763528594027, 2.486819361740209455, 2.467904050297364815, + 2.449198932978249754, 2.430698339264419694, 2.412396812688870629, 2.394289099921457886, + 2.376370140536140596, 2.358635057409337321, 2.341079147703034380, 2.323697874390196372, + 2.306486858283579799, 2.289441870532269441, 2.272558825553154804, 2.255833774367219213, + 2.239262898312909034, 2.222842503111036816, 2.206569013257663858, 2.190438966723220027, + 2.174449009937774679, 2.158595893043885994, 2.142876465399842001, 2.127287671317368289, + 2.111826546019042183, 2.096490211801715020, 2.081275874393225145, 2.066180819490575526, + 2.051202409468584786, 2.036338080248769611, 2.021585338318926173, 2.006941757894518563, + 1.992404978213576650, 1.977972700957360441, 1.963642687789548313, 1.949412758007184943, + 1.935280786297051359, 1.921244700591528076, 1.907302480018387536, 1.893452152939308242, + 1.879691795072211180, 1.866019527692827973, 1.852433515911175554, 1.838931967018879954, + 1.825513128903519799, 1.812175288526390649, 1.798916770460290859, 1.785735935484126014, + 1.772631179231305643, 1.759600930889074766, 1.746643651946074405, 1.733757834985571566, + 1.720942002521935299, 1.708194705878057773, 1.695514524101537912, 1.682900062917553896, + 1.670349953716452118, 1.657862852574172763, 1.645437439303723659, 1.633072416535991334, + 1.620766508828257901, 1.608518461798858379, 1.596327041286483395, 1.584191032532688892, + 1.572109239386229707, 1.560080483527888084, 1.548103603714513499, 1.536177455041032092, + 1.524300908219226258, 1.512472848872117082, 1.500692176842816750, 1.488957805516746058, + 1.477268661156133867, 1.465623682245745352, 1.454021818848793446, 1.442462031972012504, + 1.430943292938879674, 1.419464582769983219, 1.408024891569535697, 1.396623217917042137, + 1.385258568263121992, 1.373929956328490576, 1.362636402505086775, 1.351376933258335189, + 1.340150580529504643, 1.328956381137116560, 1.317793376176324749, 1.306660610415174117, + 1.295557131686601027, 1.284481990275012642, 1.273434238296241139, 1.262412929069615330, + 1.251417116480852521, 1.240445854334406572, 1.229498195693849105, 1.218573192208790124, + 1.207669893426761121, 1.196787346088403092, 1.185924593404202199, 1.175080674310911677, + 1.164254622705678921, 1.153445466655774743, 1.142652227581672841, 1.131873919411078511, + 1.121109547701330200, 1.110358108727411031, 1.099618588532597308, 1.088889961938546813, + 1.078171191511372307, 1.067461226479967662, 1.056759001602551429, 1.046063435977044209, + 1.035373431790528542, 1.024687873002617211, 1.014005623957096480, 1.003325527915696735, + 0.992646405507275897, 0.981967053085062602, 0.971286240983903260, 0.960602711668666509, + 0.949915177764075969, 0.939222319955262286, 0.928522784747210395, 0.917815182070044311, + 0.907098082715690257, 0.896370015589889935, 0.885629464761751528, 0.874874866291025066, + 0.864104604811004484, 0.853317009842373353, 0.842510351810368485, 0.831682837734273206, + 0.820832606554411814, 0.809957724057418282, 0.799056177355487174, 0.788125868869492430, + 0.777164609759129710, 0.766170112735434672, 0.755139984181982249, 0.744071715500508102, + 0.732962673584365398, 0.721810090308756203, 0.710611050909655040, 0.699362481103231959, + 0.688061132773747808, 0.676703568029522584, 0.665286141392677943, 0.653804979847664947, + 0.642255960424536365, 0.630634684933490286, 0.618936451394876075, 0.607156221620300030, + 0.595288584291502887, 0.583327712748769489, 0.571267316532588332, 0.559100585511540626, + 0.546820125163310577, 0.534417881237165604, 0.521885051592135052, 0.509211982443654398, + 0.496388045518671162, 0.483401491653461857, 0.470239275082169006, 0.456886840931420235, + 0.443327866073552401, 0.429543940225410703, 0.415514169600356364, 0.401214678896277765, + 0.386617977941119573, 0.371692145329917234, 0.356399760258393816, 0.340696481064849122, + 0.324529117016909452, 0.307832954674932158, 0.290527955491230394, 0.272513185478464703, + 0.253658363385912022, 0.233790483059674731, 0.212671510630966620, 0.189958689622431842, + 0.165127622564187282, 0.137304980940012589, 0.104838507565818778, 0.063852163815001570, + 0.000000000000000000]; +pub static ZIG_EXP_F: [f64; 257] = + [0.000167066692307963, 0.000454134353841497, 0.000967269282327174, 0.001536299780301573, + 0.002145967743718907, 0.002788798793574076, 0.003460264777836904, 0.004157295120833797, + 0.004877655983542396, 0.005619642207205489, 0.006381905937319183, 0.007163353183634991, + 0.007963077438017043, 0.008780314985808977, 0.009614413642502212, 0.010464810181029981, + 0.011331013597834600, 0.012212592426255378, 0.013109164931254991, 0.014020391403181943, + 0.014945968011691148, 0.015885621839973156, 0.016839106826039941, 0.017806200410911355, + 0.018786700744696024, 0.019780424338009740, 0.020787204072578114, 0.021806887504283581, + 0.022839335406385240, 0.023884420511558174, 0.024942026419731787, 0.026012046645134221, + 0.027094383780955803, 0.028188948763978646, 0.029295660224637411, 0.030414443910466622, + 0.031545232172893622, 0.032687963508959555, 0.033842582150874358, 0.035009037697397431, + 0.036187284781931443, 0.037377282772959382, 0.038578995503074871, 0.039792391023374139, + 0.041017441380414840, 0.042254122413316254, 0.043502413568888197, 0.044762297732943289, + 0.046033761076175184, 0.047316792913181561, 0.048611385573379504, 0.049917534282706379, + 0.051235237055126281, 0.052564494593071685, 0.053905310196046080, 0.055257689676697030, + 0.056621641283742870, 0.057997175631200659, 0.059384305633420280, 0.060783046445479660, + 0.062193415408541036, 0.063615431999807376, 0.065049117786753805, 0.066494496385339816, + 0.067951593421936643, 0.069420436498728783, 0.070901055162371843, 0.072393480875708752, + 0.073897746992364746, 0.075413888734058410, 0.076941943170480517, 0.078481949201606435, + 0.080033947542319905, 0.081597980709237419, 0.083174093009632397, 0.084762330532368146, + 0.086362741140756927, 0.087975374467270231, 0.089600281910032886, 0.091237516631040197, + 0.092887133556043569, 0.094549189376055873, 0.096223742550432825, 0.097910853311492213, + 0.099610583670637132, 0.101322997425953631, 0.103048160171257702, 0.104786139306570145, + 0.106537004050001632, 0.108300825451033755, 0.110077676405185357, 0.111867631670056283, + 0.113670767882744286, 0.115487163578633506, 0.117316899211555525, 0.119160057175327641, + 0.121016721826674792, 0.122886979509545108, 0.124770918580830933, 0.126668629437510671, + 0.128580204545228199, 0.130505738468330773, 0.132445327901387494, 0.134399071702213602, + 0.136367070926428829, 0.138349428863580176, 0.140346251074862399, 0.142357645432472146, + 0.144383722160634720, 0.146424593878344889, 0.148480375643866735, 0.150551185001039839, + 0.152637142027442801, 0.154738369384468027, 0.156854992369365148, 0.158987138969314129, + 0.161134939917591952, 0.163298528751901734, 0.165478041874935922, 0.167673618617250081, + 0.169885401302527550, 0.172113535315319977, 0.174358169171353411, 0.176619454590494829, + 0.178897546572478278, 0.181192603475496261, 0.183504787097767436, 0.185834262762197083, + 0.188181199404254262, 0.190545769663195363, 0.192928149976771296, 0.195328520679563189, + 0.197747066105098818, 0.200183974691911210, 0.202639439093708962, 0.205113656293837654, + 0.207606827724221982, 0.210119159388988230, 0.212650861992978224, 0.215202151075378628, + 0.217773247148700472, 0.220364375843359439, 0.222975768058120111, 0.225607660116683956, + 0.228260293930716618, 0.230933917169627356, 0.233628783437433291, 0.236345152457059560, + 0.239083290262449094, 0.241843469398877131, 0.244625969131892024, 0.247431075665327543, + 0.250259082368862240, 0.253110290015629402, 0.255985007030415324, 0.258883549749016173, + 0.261806242689362922, 0.264753418835062149, 0.267725419932044739, 0.270722596799059967, + 0.273745309652802915, 0.276793928448517301, 0.279868833236972869, 0.282970414538780746, + 0.286099073737076826, 0.289255223489677693, 0.292439288161892630, 0.295651704281261252, + 0.298892921015581847, 0.302163400675693528, 0.305463619244590256, 0.308794066934560185, + 0.312155248774179606, 0.315547685227128949, 0.318971912844957239, 0.322428484956089223, + 0.325917972393556354, 0.329440964264136438, 0.332998068761809096, 0.336589914028677717, + 0.340217149066780189, 0.343880444704502575, 0.347580494621637148, 0.351318016437483449, + 0.355093752866787626, 0.358908472948750001, 0.362762973354817997, 0.366658079781514379, + 0.370594648435146223, 0.374573567615902381, 0.378595759409581067, 0.382662181496010056, + 0.386773829084137932, 0.390931736984797384, 0.395136981833290435, 0.399390684475231350, + 0.403694012530530555, 0.408048183152032673, 0.412454465997161457, 0.416914186433003209, + 0.421428728997616908, 0.425999541143034677, 0.430628137288459167, 0.435316103215636907, + 0.440065100842354173, 0.444876873414548846, 0.449753251162755330, 0.454696157474615836, + 0.459707615642138023, 0.464789756250426511, 0.469944825283960310, 0.475175193037377708, + 0.480483363930454543, 0.485871987341885248, 0.491343869594032867, 0.496901987241549881, + 0.502549501841348056, 0.508289776410643213, 0.514126393814748894, 0.520063177368233931, + 0.526104213983620062, 0.532253880263043655, 0.538516872002862246, 0.544898237672440056, + 0.551403416540641733, 0.558038282262587892, 0.564809192912400615, 0.571723048664826150, + 0.578787358602845359, 0.586010318477268366, 0.593400901691733762, 0.600968966365232560, + 0.608725382079622346, 0.616682180915207878, 0.624852738703666200, 0.633251994214366398, + 0.641896716427266423, 0.650805833414571433, 0.660000841079000145, 0.669506316731925177, + 0.679350572264765806, 0.689566496117078431, 0.700192655082788606, 0.711274760805076456, + 0.722867659593572465, 0.735038092431424039, 0.747868621985195658, 0.761463388849896838, + 0.775956852040116218, 0.791527636972496285, 0.808421651523009044, 0.826993296643051101, + 0.847785500623990496, 0.871704332381204705, 0.900469929925747703, 0.938143680862176477, + 1.000000000000000000]; diff --git a/rand/src/jitter.rs b/rand/src/jitter.rs new file mode 100644 index 0000000..3693481 --- /dev/null +++ b/rand/src/jitter.rs @@ -0,0 +1,754 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. +// +// Based on jitterentropy-library, http://www.chronox.de/jent.html. +// Copyright Stephan Mueller <smueller@chronox.de>, 2014 - 2017. +// +// With permission from Stephan Mueller to relicense the Rust translation under +// the MIT license. + +//! Non-physical true random number generator based on timing jitter. + +use Rng; + +use core::{fmt, mem, ptr}; +#[cfg(feature="std")] +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; + +const MEMORY_BLOCKS: usize = 64; +const MEMORY_BLOCKSIZE: usize = 32; +const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE; + +/// A true random number generator based on jitter in the CPU execution time, +/// and jitter in memory access time. +/// +/// This is a true random number generator, as opposed to pseudo-random +/// generators. Random numbers generated by `JitterRng` can be seen as fresh +/// entropy. A consequence is that is orders of magnitude slower than `OsRng` +/// and PRNGs (about 10^3 .. 10^6 slower). +/// +/// There are very few situations where using this RNG is appropriate. Only very +/// few applications require true entropy. A normal PRNG can be statistically +/// indistinguishable, and a cryptographic PRNG should also be as impossible to +/// predict. +/// +/// Use of `JitterRng` is recommended for initializing cryptographic PRNGs when +/// `OsRng` is not available. +/// +/// This implementation is based on +/// [Jitterentropy](http://www.chronox.de/jent.html) version 2.1.0. +// +// Note: the C implementation relies on being compiled without optimizations. +// This implementation goes through lengths to make the compiler not optimise +// out what is technically dead code, but that does influence timing jitter. +pub struct JitterRng { + data: u64, // Actual random number + // Number of rounds to run the entropy collector per 64 bits + rounds: u32, + // Timer and previous time stamp, used by `measure_jitter` + timer: fn() -> u64, + prev_time: u64, + // Deltas used for the stuck test + last_delta: i64, + last_delta2: i64, + // Memory for the Memory Access noise source + mem_prev_index: usize, + mem: [u8; MEMORY_SIZE], + // Make `next_u32` not waste 32 bits + data_remaining: Option<u32>, +} + +// Custom Debug implementation that does not expose the internal state +impl fmt::Debug for JitterRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "JitterRng {{}}") + } +} + +/// An error that can occur when `test_timer` fails. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TimerError { + /// No timer available. + NoTimer, + /// Timer too coarse to use as an entropy source. + CoarseTimer, + /// Timer is not monotonically increasing. + NotMonotonic, + /// Variations of deltas of time too small. + TinyVariantions, + /// Too many stuck results (indicating no added entropy). + TooManyStuck, + #[doc(hidden)] + __Nonexhaustive, +} + +impl TimerError { + fn description(&self) -> &'static str { + match *self { + TimerError::NoTimer => "no timer available", + TimerError::CoarseTimer => "coarse timer", + TimerError::NotMonotonic => "timer not monotonic", + TimerError::TinyVariantions => "time delta variations too small", + TimerError::TooManyStuck => "too many stuck results", + TimerError::__Nonexhaustive => unreachable!(), + } + } +} + +impl fmt::Display for TimerError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +#[cfg(feature="std")] +impl ::std::error::Error for TimerError { + fn description(&self) -> &str { + self.description() + } +} + +// Initialise to zero; must be positive +#[cfg(feature="std")] +static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT; + +impl JitterRng { + /// Create a new `JitterRng`. + /// Makes use of `std::time` for a timer. + /// + /// During initialization CPU execution timing jitter is measured a few + /// hundred times. If this does not pass basic quality tests, an error is + /// returned. The test result is cached to make subsequent calls faster. + #[cfg(feature="std")] + pub fn new() -> Result<JitterRng, TimerError> { + let mut ec = JitterRng::new_with_timer(platform::get_nstime); + let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u32; + if rounds == 0 { + // No result yet: run test. + // This allows the timer test to run multiple times; we don't care. + rounds = ec.test_timer()?; + JITTER_ROUNDS.store(rounds as usize, Ordering::Relaxed); + } + ec.set_rounds(rounds); + Ok(ec) + } + + /// Create a new `JitterRng`. + /// A custom timer can be supplied, making it possible to use `JitterRng` in + /// `no_std` environments. + /// + /// The timer must have nanosecond precision. + /// + /// This method is more low-level than `new()`. It is the responsibility of + /// the caller to run `test_timer` before using any numbers generated with + /// `JitterRng`, and optionally call `set_rounds()`. + pub fn new_with_timer(timer: fn() -> u64) -> JitterRng { + let mut ec = JitterRng { + data: 0, + rounds: 64, + timer: timer, + prev_time: 0, + last_delta: 0, + last_delta2: 0, + mem_prev_index: 0, + mem: [0; MEMORY_SIZE], + data_remaining: None, + }; + + // Fill `data`, `prev_time`, `last_delta` and `last_delta2` with + // non-zero values. + ec.prev_time = timer(); + ec.gen_entropy(); + + // Do a single read from `self.mem` to make sure the Memory Access noise + // source is not optimised out. + // Note: this read is important, it effects optimisations for the entire + // module! + black_box(ec.mem[0]); + + ec + } + + /// Configures how many rounds are used to generate each 64-bit value. + /// This must be greater than zero, and has a big impact on performance + /// and output quality. + /// + /// `new_with_timer` conservatively uses 64 rounds, but often less rounds + /// can be used. The `test_timer()` function returns the minimum number of + /// rounds required for full strength (platform dependent), so one may use + /// `rng.set_rounds(rng.test_timer()?);` or cache the value. + pub fn set_rounds(&mut self, rounds: u32) { + assert!(rounds > 0); + self.rounds = rounds; + } + + // Calculate a random loop count used for the next round of an entropy + // collection, based on bits from a fresh value from the timer. + // + // The timer is folded to produce a number that contains at most `n_bits` + // bits. + // + // Note: A constant should be added to the resulting random loop count to + // prevent loops that run 0 times. + #[inline(never)] + fn random_loop_cnt(&mut self, n_bits: u32) -> u32 { + let mut rounds = 0; + + let mut time = (self.timer)(); + // Mix with the current state of the random number balance the random + // loop counter a bit more. + time ^= self.data; + + // We fold the time value as much as possible to ensure that as many + // bits of the time stamp are included as possible. + let folds = (64 + n_bits - 1) / n_bits; + let mask = (1 << n_bits) - 1; + for _ in 0..folds { + rounds ^= time & mask; + time = time >> n_bits; + } + + rounds as u32 + } + + // CPU jitter noise source + // Noise source based on the CPU execution time jitter + // + // This function injects the individual bits of the time value into the + // entropy pool using an LFSR. + // + // The code is deliberately inefficient with respect to the bit shifting. + // This function not only acts as folding operation, but this function's + // execution is used to measure the CPU execution time jitter. Any change to + // the loop in this function implies that careful retesting must be done. + #[inline(never)] + fn lfsr_time(&mut self, time: u64, var_rounds: bool) { + fn lfsr(mut data: u64, time: u64) -> u64{ + for i in 1..65 { + let mut tmp = time << (64 - i); + tmp = tmp >> (64 - 1); + + // Fibonacci LSFR with polynomial of + // x^64 + x^61 + x^56 + x^31 + x^28 + x^23 + 1 which is + // primitive according to + // http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf + // (the shift values are the polynomial values minus one + // due to counting bits from 0 to 63). As the current + // position is always the LSB, the polynomial only needs + // to shift data in from the left without wrap. + data ^= tmp; + data ^= (data >> 63) & 1; + data ^= (data >> 60) & 1; + data ^= (data >> 55) & 1; + data ^= (data >> 30) & 1; + data ^= (data >> 27) & 1; + data ^= (data >> 22) & 1; + data = data.rotate_left(1); + } + data + } + + // Note: in the reference implementation only the last round effects + // `self.data`, all the other results are ignored. To make sure the + // other rounds are not optimised out, we first run all but the last + // round on a throw-away value instead of the real `self.data`. + let mut lfsr_loop_cnt = 0; + if var_rounds { lfsr_loop_cnt = self.random_loop_cnt(4) }; + + let mut throw_away: u64 = 0; + for _ in 0..lfsr_loop_cnt { + throw_away = lfsr(throw_away, time); + } + black_box(throw_away); + + self.data = lfsr(self.data, time); + } + + // Memory Access noise source + // This is a noise source based on variations in memory access times + // + // This function performs memory accesses which will add to the timing + // variations due to an unknown amount of CPU wait states that need to be + // added when accessing memory. The memory size should be larger than the L1 + // caches as outlined in the documentation and the associated testing. + // + // The L1 cache has a very high bandwidth, albeit its access rate is usually + // slower than accessing CPU registers. Therefore, L1 accesses only add + // minimal variations as the CPU has hardly to wait. Starting with L2, + // significant variations are added because L2 typically does not belong to + // the CPU any more and therefore a wider range of CPU wait states is + // necessary for accesses. L3 and real memory accesses have even a wider + // range of wait states. However, to reliably access either L3 or memory, + // the `self.mem` memory must be quite large which is usually not desirable. + #[inline(never)] + fn memaccess(&mut self, var_rounds: bool) { + let mut acc_loop_cnt = 128; + if var_rounds { acc_loop_cnt += self.random_loop_cnt(4) }; + + let mut index = self.mem_prev_index; + for _ in 0..acc_loop_cnt { + // Addition of memblocksize - 1 to index with wrap around logic to + // ensure that every memory location is hit evenly. + // The modulus also allows the compiler to remove the indexing + // bounds check. + index = (index + MEMORY_BLOCKSIZE - 1) % MEMORY_SIZE; + + // memory access: just add 1 to one byte + // memory access implies read from and write to memory location + let tmp = self.mem[index]; + self.mem[index] = tmp.wrapping_add(1); + } + self.mem_prev_index = index; + } + + + // Stuck test by checking the: + // - 1st derivation of the jitter measurement (time delta) + // - 2nd derivation of the jitter measurement (delta of time deltas) + // - 3rd derivation of the jitter measurement (delta of delta of time + // deltas) + // + // All values must always be non-zero. + // This test is a heuristic to see whether the last measurement holds + // entropy. + fn stuck(&mut self, current_delta: i64) -> bool { + let delta2 = self.last_delta - current_delta; + let delta3 = delta2 - self.last_delta2; + + self.last_delta = current_delta; + self.last_delta2 = delta2; + + current_delta == 0 || delta2 == 0 || delta3 == 0 + } + + // This is the heart of the entropy generation: calculate time deltas and + // use the CPU jitter in the time deltas. The jitter is injected into the + // entropy pool. + // + // Ensure that `self.prev_time` is primed before using the output of this + // function. This can be done by calling this function and not using its + // result. + fn measure_jitter(&mut self) -> Option<()> { + // Invoke one noise source before time measurement to add variations + self.memaccess(true); + + // Get time stamp and calculate time delta to previous + // invocation to measure the timing variations + let time = (self.timer)(); + // Note: wrapping_sub combined with a cast to `i64` generates a correct + // delta, even in the unlikely case this is a timer that is not strictly + // monotonic. + let current_delta = time.wrapping_sub(self.prev_time) as i64; + self.prev_time = time; + + // Call the next noise source which also injects the data + self.lfsr_time(current_delta as u64, true); + + // Check whether we have a stuck measurement (i.e. does the last + // measurement holds entropy?). + if self.stuck(current_delta) { return None }; + + // Rotate the data buffer by a prime number (any odd number would + // do) to ensure that every bit position of the input time stamp + // has an even chance of being merged with a bit position in the + // entropy pool. We do not use one here as the adjacent bits in + // successive time deltas may have some form of dependency. The + // chosen value of 7 implies that the low 7 bits of the next + // time delta value is concatenated with the current time delta. + self.data = self.data.rotate_left(7); + + Some(()) + } + + // Shuffle the pool a bit by mixing some value with a bijective function + // (XOR) into the pool. + // + // The function generates a mixer value that depends on the bits set and + // the location of the set bits in the random number generated by the + // entropy source. Therefore, based on the generated random number, this + // mixer value can have 2^64 different values. That mixer value is + // initialized with the first two SHA-1 constants. After obtaining the + // mixer value, it is XORed into the random number. + // + // The mixer value is not assumed to contain any entropy. But due to the + // XOR operation, it can also not destroy any entropy present in the + // entropy pool. + #[inline(never)] + fn stir_pool(&mut self) { + // This constant is derived from the first two 32 bit initialization + // vectors of SHA-1 as defined in FIPS 180-4 section 5.3.1 + // The order does not really matter as we do not rely on the specific + // numbers. We just pick the SHA-1 constants as they have a good mix of + // bit set and unset. + const CONSTANT: u64 = 0x67452301efcdab89; + + // The start value of the mixer variable is derived from the third + // and fourth 32 bit initialization vector of SHA-1 as defined in + // FIPS 180-4 section 5.3.1 + let mut mixer = 0x98badcfe10325476; + + // This is a constant time function to prevent leaking timing + // information about the random number. + // The normal code is: + // ``` + // for i in 0..64 { + // if ((self.data >> i) & 1) == 1 { mixer ^= CONSTANT; } + // } + // ``` + // This is a bit fragile, as LLVM really wants to use branches here, and + // we rely on it to not recognise the opportunity. + for i in 0..64 { + let apply = (self.data >> i) & 1; + let mask = !apply.wrapping_sub(1); + mixer ^= CONSTANT & mask; + mixer = mixer.rotate_left(1); + } + + self.data ^= mixer; + } + + fn gen_entropy(&mut self) -> u64 { + // Prime `self.prev_time`, and run the noice sources to make sure the + // first loop round collects the expected entropy. + let _ = self.measure_jitter(); + + for _ in 0..self.rounds { + // If a stuck measurement is received, repeat measurement + // Note: we do not guard against an infinite loop, that would mean + // the timer suddenly became broken. + while self.measure_jitter().is_none() {} + } + + self.stir_pool(); + self.data + } + + /// Basic quality tests on the timer, by measuring CPU timing jitter a few + /// hundred times. + /// + /// If succesful, this will return the estimated number of rounds necessary + /// to collect 64 bits of entropy. Otherwise a `TimerError` with the cause + /// of the failure will be returned. + pub fn test_timer(&mut self) -> Result<u32, TimerError> { + // We could add a check for system capabilities such as `clock_getres` + // or check for `CONFIG_X86_TSC`, but it does not make much sense as the + // following sanity checks verify that we have a high-resolution timer. + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + return Err(TimerError::NoTimer); + + let mut delta_sum = 0; + let mut old_delta = 0; + + let mut time_backwards = 0; + let mut count_mod = 0; + let mut count_stuck = 0; + + // TESTLOOPCOUNT needs some loops to identify edge systems. + // 100 is definitely too little. + const TESTLOOPCOUNT: u64 = 300; + const CLEARCACHE: u64 = 100; + + for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { + // Measure time delta of core entropy collection logic + let time = (self.timer)(); + self.memaccess(true); + self.lfsr_time(time, true); + let time2 = (self.timer)(); + + // Test whether timer works + if time == 0 || time2 == 0 { + return Err(TimerError::NoTimer); + } + let delta = time2.wrapping_sub(time) as i64; + + // Test whether timer is fine grained enough to provide delta even + // when called shortly after each other -- this implies that we also + // have a high resolution timer + if delta == 0 { + return Err(TimerError::CoarseTimer); + } + + // Up to here we did not modify any variable that will be + // evaluated later, but we already performed some work. Thus we + // already have had an impact on the caches, branch prediction, + // etc. with the goal to clear it to get the worst case + // measurements. + if i < CLEARCACHE { continue; } + + if self.stuck(delta) { count_stuck += 1; } + + // Test whether we have an increasing timer. + if !(time2 > time) { time_backwards += 1; } + + // Count the number of times the counter increases in steps of 100ns + // or greater. + if (delta % 100) == 0 { count_mod += 1; } + + // Ensure that we have a varying delta timer which is necessary for + // the calculation of entropy -- perform this check only after the + // first loop is executed as we need to prime the old_delta value + delta_sum += (delta - old_delta).abs() as u64; + old_delta = delta; + } + + // We allow the time to run backwards for up to three times. + // This can happen if the clock is being adjusted by NTP operations. + // If such an operation just happens to interfere with our test, it + // should not fail. The value of 3 should cover the NTP case being + // performed during our test run. + if time_backwards > 3 { + return Err(TimerError::NotMonotonic); + } + + // Test that the available amount of entropy per round does not get to + // low. We expect 1 bit of entropy per round as a reasonable minimum + // (although less is possible, it means the collector loop has to run + // much more often). + // `assert!(delta_average >= log2(1))` + // `assert!(delta_sum / TESTLOOPCOUNT >= 1)` + // `assert!(delta_sum >= TESTLOOPCOUNT)` + if delta_sum < TESTLOOPCOUNT { + return Err(TimerError::TinyVariantions); + } + + // Ensure that we have variations in the time stamp below 100 for at + // least 10% of all checks -- on some platforms, the counter increments + // in multiples of 100, but not always + if count_mod > (TESTLOOPCOUNT * 9 / 10) { + return Err(TimerError::CoarseTimer); + } + + // If we have more than 90% stuck results, then this Jitter RNG is + // likely to not work well. + if count_stuck > (TESTLOOPCOUNT * 9 / 10) { + return Err(TimerError::TooManyStuck); + } + + // Estimate the number of `measure_jitter` rounds necessary for 64 bits + // of entropy. + // + // We don't try very hard to come up with a good estimate of the + // available bits of entropy per round here for two reasons: + // 1. Simple estimates of the available bits (like Shannon entropy) are + // too optimistic. + // 2) Unless we want to waste a lot of time during intialization, there + // only a small number of samples are available. + // + // Therefore we use a very simple and conservative estimate: + // `let bits_of_entropy = log2(delta_average) / 2`. + // + // The number of rounds `measure_jitter` should run to collect 64 bits + // of entropy is `64 / bits_of_entropy`. + // + // To have smaller rounding errors, intermediate values are multiplied + // by `FACTOR`. To compensate for `log2` and division rounding down, + // add 1. + let delta_average = delta_sum / TESTLOOPCOUNT; + // println!("delta_average: {}", delta_average); + + const FACTOR: u32 = 3; + fn log2(x: u64) -> u32 { 64 - x.leading_zeros() } + + // pow(δ, FACTOR) must be representable; if you have overflow reduce FACTOR + Ok(64 * 2 * FACTOR / (log2(delta_average.pow(FACTOR)) + 1)) + } + + /// Statistical test: return the timer delta of one normal run of the + /// `JitterEntropy` entropy collector. + /// + /// Setting `var_rounds` to `true` will execute the memory access and the + /// CPU jitter noice sources a variable amount of times (just like a real + /// `JitterEntropy` round). + /// + /// Setting `var_rounds` to `false` will execute the noice sources the + /// minimal number of times. This can be used to measure the minimum amount + /// of entropy one round of entropy collector can collect in the worst case. + /// + /// # Example + /// + /// Use `timer_stats` to run the [NIST SP 800-90B Entropy Estimation Suite] + /// (https://github.com/usnistgov/SP800-90B_EntropyAssessment). + /// + /// This is the recommended way to test the quality of `JitterRng`. It + /// should be run before using the RNG on untested hardware, after changes + /// that could effect how the code is optimised, and after major compiler + /// compiler changes, like a new LLVM version. + /// + /// First generate two files `jitter_rng_var.bin` and `jitter_rng_var.min`. + /// + /// Execute `python noniid_main.py -v jitter_rng_var.bin 8`, and validate it + /// with `restart.py -v jitter_rng_var.bin 8 <min-entropy>`. + /// This number is the expected amount of entropy that is at least available + /// for each round of the entropy collector. This number should be greater + /// than the amount estimated with `64 / test_timer()`. + /// + /// Execute `python noniid_main.py -v -u 4 jitter_rng_var.bin 4`, and + /// validate it with `restart.py -v -u 4 jitter_rng_var.bin 4 <min-entropy>`. + /// This number is the expected amount of entropy that is available in the + /// last 4 bits of the timer delta after running noice sources. Note that + /// a value of 3.70 is the minimum estimated entropy for true randomness. + /// + /// Execute `python noniid_main.py -v -u 4 jitter_rng_var.bin 4`, and + /// validate it with `restart.py -v -u 4 jitter_rng_var.bin 4 <min-entropy>`. + /// This number is the expected amount of entropy that is available to the + /// entropy collecter if both noice sources only run their minimal number of + /// times. This measures the absolute worst-case, and gives a lower bound + /// for the available entropy. + /// + /// ```rust,no_run + /// use rand::JitterRng; + /// + /// # use std::error::Error; + /// # use std::fs::File; + /// # use std::io::Write; + /// # + /// # fn try_main() -> Result<(), Box<Error>> { + /// fn get_nstime() -> u64 { + /// use std::time::{SystemTime, UNIX_EPOCH}; + /// + /// let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + /// // The correct way to calculate the current time is + /// // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64` + /// // But this is faster, and the difference in terms of entropy is + /// // negligible (log2(10^9) == 29.9). + /// dur.as_secs() << 30 | dur.subsec_nanos() as u64 + /// } + /// + /// // Do not initialize with `JitterRng::new`, but with `new_with_timer`. + /// // 'new' always runst `test_timer`, and can therefore fail to + /// // initialize. We want to be able to get the statistics even when the + /// // timer test fails. + /// let mut rng = JitterRng::new_with_timer(get_nstime); + /// + /// // 1_000_000 results are required for the NIST SP 800-90B Entropy + /// // Estimation Suite + /// // FIXME: this number is smaller here, otherwise the Doc-test is too slow + /// const ROUNDS: usize = 10_000; + /// let mut deltas_variable: Vec<u8> = Vec::with_capacity(ROUNDS); + /// let mut deltas_minimal: Vec<u8> = Vec::with_capacity(ROUNDS); + /// + /// for _ in 0..ROUNDS { + /// deltas_variable.push(rng.timer_stats(true) as u8); + /// deltas_minimal.push(rng.timer_stats(false) as u8); + /// } + /// + /// // Write out after the statistics collection loop, to not disturb the + /// // test results. + /// File::create("jitter_rng_var.bin")?.write(&deltas_variable)?; + /// File::create("jitter_rng_min.bin")?.write(&deltas_minimal)?; + /// # + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + #[cfg(feature="std")] + pub fn timer_stats(&mut self, var_rounds: bool) -> i64 { + let time = platform::get_nstime(); + self.memaccess(var_rounds); + self.lfsr_time(time, var_rounds); + let time2 = platform::get_nstime(); + time2.wrapping_sub(time) as i64 + } +} + +#[cfg(feature="std")] +mod platform { + #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))] + pub fn get_nstime() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + + let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + // The correct way to calculate the current time is + // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64` + // But this is faster, and the difference in terms of entropy is negligible + // (log2(10^9) == 29.9). + dur.as_secs() << 30 | dur.subsec_nanos() as u64 + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub fn get_nstime() -> u64 { + extern crate libc; + // On Mac OS and iOS std::time::SystemTime only has 1000ns resolution. + // We use `mach_absolute_time` instead. This provides a CPU dependent unit, + // to get real nanoseconds the result should by multiplied by numer/denom + // from `mach_timebase_info`. + // But we are not interested in the exact nanoseconds, just entropy. So we + // use the raw result. + unsafe { libc::mach_absolute_time() } + } + + #[cfg(target_os = "windows")] + pub fn get_nstime() -> u64 { + extern crate winapi; + unsafe { + let mut t = super::mem::zeroed(); + winapi::um::profileapi::QueryPerformanceCounter(&mut t); + *t.QuadPart() as u64 + } + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn get_nstime() -> u64 { + unreachable!() + } +} + +// A function that is opaque to the optimizer to assist in avoiding dead-code +// elimination. Taken from `bencher`. +fn black_box<T>(dummy: T) -> T { + unsafe { + let ret = ptr::read_volatile(&dummy); + mem::forget(dummy); + ret + } +} + +impl Rng for JitterRng { + fn next_u32(&mut self) -> u32 { + // We want to use both parts of the generated entropy + if let Some(high) = self.data_remaining.take() { + high + } else { + let data = self.next_u64(); + self.data_remaining = Some((data >> 32) as u32); + data as u32 + } + } + + fn next_u64(&mut self) -> u64 { + self.gen_entropy() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + let mut left = dest; + while left.len() >= 8 { + let (l, r) = {left}.split_at_mut(8); + left = r; + let chunk: [u8; 8] = unsafe { + mem::transmute(self.next_u64().to_le()) + }; + l.copy_from_slice(&chunk); + } + let n = left.len(); + if n > 0 { + let chunk: [u8; 8] = unsafe { + mem::transmute(self.next_u64().to_le()) + }; + left.copy_from_slice(&chunk[..n]); + } + } +} + +// There are no tests included because (1) this is an "external" RNG, so output +// is not reproducible and (2) `test_timer` *will* fail on some platforms. diff --git a/rand/src/lib.rs b/rand/src/lib.rs new file mode 100644 index 0000000..7b22dd4 --- /dev/null +++ b/rand/src/lib.rs @@ -0,0 +1,1214 @@ +// Copyright 2013-2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Utilities for random number generation +//! +//! The key functions are `random()` and `Rng::gen()`. These are polymorphic and +//! so can be used to generate any type that implements `Rand`. Type inference +//! means that often a simple call to `rand::random()` or `rng.gen()` will +//! suffice, but sometimes an annotation is required, e.g. +//! `rand::random::<f64>()`. +//! +//! See the `distributions` submodule for sampling random numbers from +//! distributions like normal and exponential. +//! +//! # Usage +//! +//! This crate is [on crates.io](https://crates.io/crates/rand) and can be +//! used by adding `rand` to the dependencies in your project's `Cargo.toml`. +//! +//! ```toml +//! [dependencies] +//! rand = "0.4" +//! ``` +//! +//! and this to your crate root: +//! +//! ```rust +//! extern crate rand; +//! ``` +//! +//! # Thread-local RNG +//! +//! There is built-in support for a RNG associated with each thread stored +//! in thread-local storage. This RNG can be accessed via `thread_rng`, or +//! used implicitly via `random`. This RNG is normally randomly seeded +//! from an operating-system source of randomness, e.g. `/dev/urandom` on +//! Unix systems, and will automatically reseed itself from this source +//! after generating 32 KiB of random data. +//! +//! # Cryptographic security +//! +//! An application that requires an entropy source for cryptographic purposes +//! must use `OsRng`, which reads randomness from the source that the operating +//! system provides (e.g. `/dev/urandom` on Unixes or `CryptGenRandom()` on +//! Windows). +//! The other random number generators provided by this module are not suitable +//! for such purposes. +//! +//! *Note*: many Unix systems provide `/dev/random` as well as `/dev/urandom`. +//! This module uses `/dev/urandom` for the following reasons: +//! +//! - On Linux, `/dev/random` may block if entropy pool is empty; +//! `/dev/urandom` will not block. This does not mean that `/dev/random` +//! provides better output than `/dev/urandom`; the kernel internally runs a +//! cryptographically secure pseudorandom number generator (CSPRNG) based on +//! entropy pool for random number generation, so the "quality" of +//! `/dev/random` is not better than `/dev/urandom` in most cases. However, +//! this means that `/dev/urandom` can yield somewhat predictable randomness +//! if the entropy pool is very small, such as immediately after first +//! booting. Linux 3.17 added the `getrandom(2)` system call which solves +//! the issue: it blocks if entropy pool is not initialized yet, but it does +//! not block once initialized. `OsRng` tries to use `getrandom(2)` if +//! available, and use `/dev/urandom` fallback if not. If an application +//! does not have `getrandom` and likely to be run soon after first booting, +//! or on a system with very few entropy sources, one should consider using +//! `/dev/random` via `ReadRng`. +//! - On some systems (e.g. FreeBSD, OpenBSD and Mac OS X) there is no +//! difference between the two sources. (Also note that, on some systems +//! e.g. FreeBSD, both `/dev/random` and `/dev/urandom` may block once if +//! the CSPRNG has not seeded yet.) +//! +//! # Examples +//! +//! ```rust +//! use rand::Rng; +//! +//! let mut rng = rand::thread_rng(); +//! if rng.gen() { // random bool +//! println!("i32: {}, u32: {}", rng.gen::<i32>(), rng.gen::<u32>()) +//! } +//! ``` +//! +//! ```rust +//! let tuple = rand::random::<(f64, char)>(); +//! println!("{:?}", tuple) +//! ``` +//! +//! ## Monte Carlo estimation of π +//! +//! For this example, imagine we have a square with sides of length 2 and a unit +//! circle, both centered at the origin. Since the area of a unit circle is π, +//! we have: +//! +//! ```text +//! (area of unit circle) / (area of square) = π / 4 +//! ``` +//! +//! So if we sample many points randomly from the square, roughly π / 4 of them +//! should be inside the circle. +//! +//! We can use the above fact to estimate the value of π: pick many points in +//! the square at random, calculate the fraction that fall within the circle, +//! and multiply this fraction by 4. +//! +//! ``` +//! use rand::distributions::{IndependentSample, Range}; +//! +//! fn main() { +//! let between = Range::new(-1f64, 1.); +//! let mut rng = rand::thread_rng(); +//! +//! let total = 1_000_000; +//! let mut in_circle = 0; +//! +//! for _ in 0..total { +//! let a = between.ind_sample(&mut rng); +//! let b = between.ind_sample(&mut rng); +//! if a*a + b*b <= 1. { +//! in_circle += 1; +//! } +//! } +//! +//! // prints something close to 3.14159... +//! println!("{}", 4. * (in_circle as f64) / (total as f64)); +//! } +//! ``` +//! +//! ## Monty Hall Problem +//! +//! This is a simulation of the [Monty Hall Problem][]: +//! +//! > Suppose you're on a game show, and you're given the choice of three doors: +//! > Behind one door is a car; behind the others, goats. You pick a door, say +//! > No. 1, and the host, who knows what's behind the doors, opens another +//! > door, say No. 3, which has a goat. He then says to you, "Do you want to +//! > pick door No. 2?" Is it to your advantage to switch your choice? +//! +//! The rather unintuitive answer is that you will have a 2/3 chance of winning +//! if you switch and a 1/3 chance of winning if you don't, so it's better to +//! switch. +//! +//! This program will simulate the game show and with large enough simulation +//! steps it will indeed confirm that it is better to switch. +//! +//! [Monty Hall Problem]: http://en.wikipedia.org/wiki/Monty_Hall_problem +//! +//! ``` +//! use rand::Rng; +//! use rand::distributions::{IndependentSample, Range}; +//! +//! struct SimulationResult { +//! win: bool, +//! switch: bool, +//! } +//! +//! // Run a single simulation of the Monty Hall problem. +//! fn simulate<R: Rng>(random_door: &Range<u32>, rng: &mut R) +//! -> SimulationResult { +//! let car = random_door.ind_sample(rng); +//! +//! // This is our initial choice +//! let mut choice = random_door.ind_sample(rng); +//! +//! // The game host opens a door +//! let open = game_host_open(car, choice, rng); +//! +//! // Shall we switch? +//! let switch = rng.gen(); +//! if switch { +//! choice = switch_door(choice, open); +//! } +//! +//! SimulationResult { win: choice == car, switch: switch } +//! } +//! +//! // Returns the door the game host opens given our choice and knowledge of +//! // where the car is. The game host will never open the door with the car. +//! fn game_host_open<R: Rng>(car: u32, choice: u32, rng: &mut R) -> u32 { +//! let choices = free_doors(&[car, choice]); +//! rand::seq::sample_slice(rng, &choices, 1)[0] +//! } +//! +//! // Returns the door we switch to, given our current choice and +//! // the open door. There will only be one valid door. +//! fn switch_door(choice: u32, open: u32) -> u32 { +//! free_doors(&[choice, open])[0] +//! } +//! +//! fn free_doors(blocked: &[u32]) -> Vec<u32> { +//! (0..3).filter(|x| !blocked.contains(x)).collect() +//! } +//! +//! fn main() { +//! // The estimation will be more accurate with more simulations +//! let num_simulations = 10000; +//! +//! let mut rng = rand::thread_rng(); +//! let random_door = Range::new(0, 3); +//! +//! let (mut switch_wins, mut switch_losses) = (0, 0); +//! let (mut keep_wins, mut keep_losses) = (0, 0); +//! +//! println!("Running {} simulations...", num_simulations); +//! for _ in 0..num_simulations { +//! let result = simulate(&random_door, &mut rng); +//! +//! match (result.win, result.switch) { +//! (true, true) => switch_wins += 1, +//! (true, false) => keep_wins += 1, +//! (false, true) => switch_losses += 1, +//! (false, false) => keep_losses += 1, +//! } +//! } +//! +//! let total_switches = switch_wins + switch_losses; +//! let total_keeps = keep_wins + keep_losses; +//! +//! println!("Switched door {} times with {} wins and {} losses", +//! total_switches, switch_wins, switch_losses); +//! +//! println!("Kept our choice {} times with {} wins and {} losses", +//! total_keeps, keep_wins, keep_losses); +//! +//! // With a large number of simulations, the values should converge to +//! // 0.667 and 0.333 respectively. +//! println!("Estimated chance to win if we switch: {}", +//! switch_wins as f32 / total_switches as f32); +//! println!("Estimated chance to win if we don't: {}", +//! keep_wins as f32 / total_keeps as f32); +//! } +//! ``` + +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://docs.rs/rand/0.4")] + +#![deny(missing_debug_implementations)] + +#![cfg_attr(not(feature="std"), no_std)] +#![cfg_attr(all(feature="alloc", not(feature="std")), feature(alloc))] +#![cfg_attr(feature = "i128_support", feature(i128_type, i128))] + +#[cfg(feature="std")] extern crate std as core; +#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc; + +use core::marker; +use core::mem; +#[cfg(feature="std")] use std::cell::RefCell; +#[cfg(feature="std")] use std::io; +#[cfg(feature="std")] use std::rc::Rc; + +// external rngs +pub use jitter::JitterRng; +#[cfg(feature="std")] pub use os::OsRng; + +// pseudo rngs +pub use isaac::{IsaacRng, Isaac64Rng}; +pub use chacha::ChaChaRng; +pub use prng::XorShiftRng; + +// local use declarations +#[cfg(target_pointer_width = "32")] +use prng::IsaacRng as IsaacWordRng; +#[cfg(target_pointer_width = "64")] +use prng::Isaac64Rng as IsaacWordRng; + +use distributions::{Range, IndependentSample}; +use distributions::range::SampleRange; + +// public modules +pub mod distributions; +pub mod jitter; +#[cfg(feature="std")] pub mod os; +#[cfg(feature="std")] pub mod read; +pub mod reseeding; +#[cfg(any(feature="std", feature = "alloc"))] pub mod seq; + +// These tiny modules are here to avoid API breakage, probably only temporarily +pub mod chacha { + //! The ChaCha random number generator. + pub use prng::ChaChaRng; +} +pub mod isaac { + //! The ISAAC random number generator. + pub use prng::{IsaacRng, Isaac64Rng}; +} + +// private modules +mod rand_impls; +mod prng; + + +/// A type that can be randomly generated using an `Rng`. +/// +/// ## Built-in Implementations +/// +/// This crate implements `Rand` for various primitive types. Assuming the +/// provided `Rng` is well-behaved, these implementations generate values with +/// the following ranges and distributions: +/// +/// * Integers (`i32`, `u32`, `isize`, `usize`, etc.): Uniformly distributed +/// over all values of the type. +/// * `char`: Uniformly distributed over all Unicode scalar values, i.e. all +/// code points in the range `0...0x10_FFFF`, except for the range +/// `0xD800...0xDFFF` (the surrogate code points). This includes +/// unassigned/reserved code points. +/// * `bool`: Generates `false` or `true`, each with probability 0.5. +/// * Floating point types (`f32` and `f64`): Uniformly distributed in the +/// half-open range `[0, 1)`. (The [`Open01`], [`Closed01`], [`Exp1`], and +/// [`StandardNormal`] wrapper types produce floating point numbers with +/// alternative ranges or distributions.) +/// +/// [`Open01`]: struct.Open01.html +/// [`Closed01`]: struct.Closed01.html +/// [`Exp1`]: distributions/exponential/struct.Exp1.html +/// [`StandardNormal`]: distributions/normal/struct.StandardNormal.html +/// +/// The following aggregate types also implement `Rand` as long as their +/// component types implement it: +/// +/// * Tuples and arrays: Each element of the tuple or array is generated +/// independently, using its own `Rand` implementation. +/// * `Option<T>`: Returns `None` with probability 0.5; otherwise generates a +/// random `T` and returns `Some(T)`. +pub trait Rand : Sized { + /// Generates a random instance of this type using the specified source of + /// randomness. + fn rand<R: Rng>(rng: &mut R) -> Self; +} + +/// A random number generator. +pub trait Rng { + /// Return the next random u32. + /// + /// This rarely needs to be called directly, prefer `r.gen()` to + /// `r.next_u32()`. + // FIXME #rust-lang/rfcs#628: Should be implemented in terms of next_u64 + fn next_u32(&mut self) -> u32; + + /// Return the next random u64. + /// + /// By default this is implemented in terms of `next_u32`. An + /// implementation of this trait must provide at least one of + /// these two methods. Similarly to `next_u32`, this rarely needs + /// to be called directly, prefer `r.gen()` to `r.next_u64()`. + fn next_u64(&mut self) -> u64 { + ((self.next_u32() as u64) << 32) | (self.next_u32() as u64) + } + + /// Return the next random f32 selected from the half-open + /// interval `[0, 1)`. + /// + /// This uses a technique described by Saito and Matsumoto at + /// MCQMC'08. Given that the IEEE floating point numbers are + /// uniformly distributed over [1,2), we generate a number in + /// this range and then offset it onto the range [0,1). Our + /// choice of bits (masking v. shifting) is arbitrary and + /// should be immaterial for high quality generators. For low + /// quality generators (ex. LCG), prefer bitshifting due to + /// correlation between sequential low order bits. + /// + /// See: + /// A PRNG specialized in double precision floating point numbers using + /// an affine transition + /// + /// * <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/ARTICLES/dSFMT.pdf> + /// * <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/dSFMT-slide-e.pdf> + /// + /// By default this is implemented in terms of `next_u32`, but a + /// random number generator which can generate numbers satisfying + /// the requirements directly can overload this for performance. + /// It is required that the return value lies in `[0, 1)`. + /// + /// See `Closed01` for the closed interval `[0,1]`, and + /// `Open01` for the open interval `(0,1)`. + fn next_f32(&mut self) -> f32 { + const UPPER_MASK: u32 = 0x3F800000; + const LOWER_MASK: u32 = 0x7FFFFF; + let tmp = UPPER_MASK | (self.next_u32() & LOWER_MASK); + let result: f32 = unsafe { mem::transmute(tmp) }; + result - 1.0 + } + + /// Return the next random f64 selected from the half-open + /// interval `[0, 1)`. + /// + /// By default this is implemented in terms of `next_u64`, but a + /// random number generator which can generate numbers satisfying + /// the requirements directly can overload this for performance. + /// It is required that the return value lies in `[0, 1)`. + /// + /// See `Closed01` for the closed interval `[0,1]`, and + /// `Open01` for the open interval `(0,1)`. + fn next_f64(&mut self) -> f64 { + const UPPER_MASK: u64 = 0x3FF0000000000000; + const LOWER_MASK: u64 = 0xFFFFFFFFFFFFF; + let tmp = UPPER_MASK | (self.next_u64() & LOWER_MASK); + let result: f64 = unsafe { mem::transmute(tmp) }; + result - 1.0 + } + + /// Fill `dest` with random data. + /// + /// This has a default implementation in terms of `next_u64` and + /// `next_u32`, but should be overridden by implementations that + /// offer a more efficient solution than just calling those + /// methods repeatedly. + /// + /// This method does *not* have a requirement to bear any fixed + /// relationship to the other methods, for example, it does *not* + /// have to result in the same output as progressively filling + /// `dest` with `self.gen::<u8>()`, and any such behaviour should + /// not be relied upon. + /// + /// This method should guarantee that `dest` is entirely filled + /// with new data, and may panic if this is impossible + /// (e.g. reading past the end of a file that is being used as the + /// source of randomness). + /// + /// # Example + /// + /// ```rust + /// use rand::{thread_rng, Rng}; + /// + /// let mut v = [0u8; 13579]; + /// thread_rng().fill_bytes(&mut v); + /// println!("{:?}", &v[..]); + /// ``` + fn fill_bytes(&mut self, dest: &mut [u8]) { + // this could, in theory, be done by transmuting dest to a + // [u64], but this is (1) likely to be undefined behaviour for + // LLVM, (2) has to be very careful about alignment concerns, + // (3) adds more `unsafe` that needs to be checked, (4) + // probably doesn't give much performance gain if + // optimisations are on. + let mut count = 0; + let mut num = 0; + for byte in dest.iter_mut() { + if count == 0 { + // we could micro-optimise here by generating a u32 if + // we only need a few more bytes to fill the vector + // (i.e. at most 4). + num = self.next_u64(); + count = 8; + } + + *byte = (num & 0xff) as u8; + num >>= 8; + count -= 1; + } + } + + /// Return a random value of a `Rand` type. + /// + /// # Example + /// + /// ```rust + /// use rand::{thread_rng, Rng}; + /// + /// let mut rng = thread_rng(); + /// let x: u32 = rng.gen(); + /// println!("{}", x); + /// println!("{:?}", rng.gen::<(f64, bool)>()); + /// ``` + #[inline(always)] + fn gen<T: Rand>(&mut self) -> T where Self: Sized { + Rand::rand(self) + } + + /// Return an iterator that will yield an infinite number of randomly + /// generated items. + /// + /// # Example + /// + /// ``` + /// use rand::{thread_rng, Rng}; + /// + /// let mut rng = thread_rng(); + /// let x = rng.gen_iter::<u32>().take(10).collect::<Vec<u32>>(); + /// println!("{:?}", x); + /// println!("{:?}", rng.gen_iter::<(f64, bool)>().take(5) + /// .collect::<Vec<(f64, bool)>>()); + /// ``` + fn gen_iter<'a, T: Rand>(&'a mut self) -> Generator<'a, T, Self> where Self: Sized { + Generator { rng: self, _marker: marker::PhantomData } + } + + /// Generate a random value in the range [`low`, `high`). + /// + /// This is a convenience wrapper around + /// `distributions::Range`. If this function will be called + /// repeatedly with the same arguments, one should use `Range`, as + /// that will amortize the computations that allow for perfect + /// uniformity, as they only happen on initialization. + /// + /// # Panics + /// + /// Panics if `low >= high`. + /// + /// # Example + /// + /// ```rust + /// use rand::{thread_rng, Rng}; + /// + /// let mut rng = thread_rng(); + /// let n: u32 = rng.gen_range(0, 10); + /// println!("{}", n); + /// let m: f64 = rng.gen_range(-40.0f64, 1.3e5f64); + /// println!("{}", m); + /// ``` + fn gen_range<T: PartialOrd + SampleRange>(&mut self, low: T, high: T) -> T where Self: Sized { + assert!(low < high, "Rng.gen_range called with low >= high"); + Range::new(low, high).ind_sample(self) + } + + /// Return a bool with a 1 in n chance of true + /// + /// # Example + /// + /// ```rust + /// use rand::{thread_rng, Rng}; + /// + /// let mut rng = thread_rng(); + /// println!("{}", rng.gen_weighted_bool(3)); + /// ``` + fn gen_weighted_bool(&mut self, n: u32) -> bool where Self: Sized { + n <= 1 || self.gen_range(0, n) == 0 + } + + /// Return an iterator of random characters from the set A-Z,a-z,0-9. + /// + /// # Example + /// + /// ```rust + /// use rand::{thread_rng, Rng}; + /// + /// let s: String = thread_rng().gen_ascii_chars().take(10).collect(); + /// println!("{}", s); + /// ``` + fn gen_ascii_chars<'a>(&'a mut self) -> AsciiGenerator<'a, Self> where Self: Sized { + AsciiGenerator { rng: self } + } + + /// Return a random element from `values`. + /// + /// Return `None` if `values` is empty. + /// + /// # Example + /// + /// ``` + /// use rand::{thread_rng, Rng}; + /// + /// let choices = [1, 2, 4, 8, 16, 32]; + /// let mut rng = thread_rng(); + /// println!("{:?}", rng.choose(&choices)); + /// assert_eq!(rng.choose(&choices[..0]), None); + /// ``` + fn choose<'a, T>(&mut self, values: &'a [T]) -> Option<&'a T> where Self: Sized { + if values.is_empty() { + None + } else { + Some(&values[self.gen_range(0, values.len())]) + } + } + + /// Return a mutable pointer to a random element from `values`. + /// + /// Return `None` if `values` is empty. + fn choose_mut<'a, T>(&mut self, values: &'a mut [T]) -> Option<&'a mut T> where Self: Sized { + if values.is_empty() { + None + } else { + let len = values.len(); + Some(&mut values[self.gen_range(0, len)]) + } + } + + /// Shuffle a mutable slice in place. + /// + /// This applies Durstenfeld's algorithm for the [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) + /// which produces an unbiased permutation. + /// + /// # Example + /// + /// ```rust + /// use rand::{thread_rng, Rng}; + /// + /// let mut rng = thread_rng(); + /// let mut y = [1, 2, 3]; + /// rng.shuffle(&mut y); + /// println!("{:?}", y); + /// rng.shuffle(&mut y); + /// println!("{:?}", y); + /// ``` + fn shuffle<T>(&mut self, values: &mut [T]) where Self: Sized { + let mut i = values.len(); + while i >= 2 { + // invariant: elements with index >= i have been locked in place. + i -= 1; + // lock element i in place. + values.swap(i, self.gen_range(0, i + 1)); + } + } +} + +impl<'a, R: ?Sized> Rng for &'a mut R where R: Rng { + fn next_u32(&mut self) -> u32 { + (**self).next_u32() + } + + fn next_u64(&mut self) -> u64 { + (**self).next_u64() + } + + fn next_f32(&mut self) -> f32 { + (**self).next_f32() + } + + fn next_f64(&mut self) -> f64 { + (**self).next_f64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + (**self).fill_bytes(dest) + } +} + +#[cfg(feature="std")] +impl<R: ?Sized> Rng for Box<R> where R: Rng { + fn next_u32(&mut self) -> u32 { + (**self).next_u32() + } + + fn next_u64(&mut self) -> u64 { + (**self).next_u64() + } + + fn next_f32(&mut self) -> f32 { + (**self).next_f32() + } + + fn next_f64(&mut self) -> f64 { + (**self).next_f64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + (**self).fill_bytes(dest) + } +} + +/// Iterator which will generate a stream of random items. +/// +/// This iterator is created via the [`gen_iter`] method on [`Rng`]. +/// +/// [`gen_iter`]: trait.Rng.html#method.gen_iter +/// [`Rng`]: trait.Rng.html +#[derive(Debug)] +pub struct Generator<'a, T, R:'a> { + rng: &'a mut R, + _marker: marker::PhantomData<fn() -> T>, +} + +impl<'a, T: Rand, R: Rng> Iterator for Generator<'a, T, R> { + type Item = T; + + fn next(&mut self) -> Option<T> { + Some(self.rng.gen()) + } +} + +/// Iterator which will continuously generate random ascii characters. +/// +/// This iterator is created via the [`gen_ascii_chars`] method on [`Rng`]. +/// +/// [`gen_ascii_chars`]: trait.Rng.html#method.gen_ascii_chars +/// [`Rng`]: trait.Rng.html +#[derive(Debug)] +pub struct AsciiGenerator<'a, R:'a> { + rng: &'a mut R, +} + +impl<'a, R: Rng> Iterator for AsciiGenerator<'a, R> { + type Item = char; + + fn next(&mut self) -> Option<char> { + const GEN_ASCII_STR_CHARSET: &'static [u8] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789"; + Some(*self.rng.choose(GEN_ASCII_STR_CHARSET).unwrap() as char) + } +} + +/// A random number generator that can be explicitly seeded to produce +/// the same stream of randomness multiple times. +pub trait SeedableRng<Seed>: Rng { + /// Reseed an RNG with the given seed. + /// + /// # Example + /// + /// ```rust + /// use rand::{Rng, SeedableRng, StdRng}; + /// + /// let seed: &[_] = &[1, 2, 3, 4]; + /// let mut rng: StdRng = SeedableRng::from_seed(seed); + /// println!("{}", rng.gen::<f64>()); + /// rng.reseed(&[5, 6, 7, 8]); + /// println!("{}", rng.gen::<f64>()); + /// ``` + fn reseed(&mut self, Seed); + + /// Create a new RNG with the given seed. + /// + /// # Example + /// + /// ```rust + /// use rand::{Rng, SeedableRng, StdRng}; + /// + /// let seed: &[_] = &[1, 2, 3, 4]; + /// let mut rng: StdRng = SeedableRng::from_seed(seed); + /// println!("{}", rng.gen::<f64>()); + /// ``` + fn from_seed(seed: Seed) -> Self; +} + +/// A wrapper for generating floating point numbers uniformly in the +/// open interval `(0,1)` (not including either endpoint). +/// +/// Use `Closed01` for the closed interval `[0,1]`, and the default +/// `Rand` implementation for `f32` and `f64` for the half-open +/// `[0,1)`. +/// +/// # Example +/// ```rust +/// use rand::{random, Open01}; +/// +/// let Open01(val) = random::<Open01<f32>>(); +/// println!("f32 from (0,1): {}", val); +/// ``` +#[derive(Debug)] +pub struct Open01<F>(pub F); + +/// A wrapper for generating floating point numbers uniformly in the +/// closed interval `[0,1]` (including both endpoints). +/// +/// Use `Open01` for the closed interval `(0,1)`, and the default +/// `Rand` implementation of `f32` and `f64` for the half-open +/// `[0,1)`. +/// +/// # Example +/// +/// ```rust +/// use rand::{random, Closed01}; +/// +/// let Closed01(val) = random::<Closed01<f32>>(); +/// println!("f32 from [0,1]: {}", val); +/// ``` +#[derive(Debug)] +pub struct Closed01<F>(pub F); + +/// The standard RNG. This is designed to be efficient on the current +/// platform. +#[derive(Copy, Clone, Debug)] +pub struct StdRng { + rng: IsaacWordRng, +} + +impl StdRng { + /// Create a randomly seeded instance of `StdRng`. + /// + /// This is a very expensive operation as it has to read + /// randomness from the operating system and use this in an + /// expensive seeding operation. If one is only generating a small + /// number of random numbers, or doesn't need the utmost speed for + /// generating each number, `thread_rng` and/or `random` may be more + /// appropriate. + /// + /// Reading the randomness from the OS may fail, and any error is + /// propagated via the `io::Result` return value. + #[cfg(feature="std")] + pub fn new() -> io::Result<StdRng> { + match OsRng::new() { + Ok(mut r) => Ok(StdRng { rng: r.gen() }), + Err(e1) => { + match JitterRng::new() { + Ok(mut r) => Ok(StdRng { rng: r.gen() }), + Err(_) => { + Err(e1) + } + } + } + } + } +} + +impl Rng for StdRng { + #[inline] + fn next_u32(&mut self) -> u32 { + self.rng.next_u32() + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.rng.next_u64() + } +} + +impl<'a> SeedableRng<&'a [usize]> for StdRng { + fn reseed(&mut self, seed: &'a [usize]) { + // the internal RNG can just be seeded from the above + // randomness. + self.rng.reseed(unsafe {mem::transmute(seed)}) + } + + fn from_seed(seed: &'a [usize]) -> StdRng { + StdRng { rng: SeedableRng::from_seed(unsafe {mem::transmute(seed)}) } + } +} + +/// Create a weak random number generator with a default algorithm and seed. +/// +/// It returns the fastest `Rng` algorithm currently available in Rust without +/// consideration for cryptography or security. If you require a specifically +/// seeded `Rng` for consistency over time you should pick one algorithm and +/// create the `Rng` yourself. +/// +/// This will seed the generator with randomness from thread_rng. +#[cfg(feature="std")] +pub fn weak_rng() -> XorShiftRng { + thread_rng().gen() +} + +/// Controls how the thread-local RNG is reseeded. +#[cfg(feature="std")] +#[derive(Debug)] +struct ThreadRngReseeder; + +#[cfg(feature="std")] +impl reseeding::Reseeder<StdRng> for ThreadRngReseeder { + fn reseed(&mut self, rng: &mut StdRng) { + match StdRng::new() { + Ok(r) => *rng = r, + Err(e) => panic!("No entropy available: {}", e), + } + } +} +#[cfg(feature="std")] +const THREAD_RNG_RESEED_THRESHOLD: u64 = 32_768; +#[cfg(feature="std")] +type ThreadRngInner = reseeding::ReseedingRng<StdRng, ThreadRngReseeder>; + +/// The thread-local RNG. +#[cfg(feature="std")] +#[derive(Clone, Debug)] +pub struct ThreadRng { + rng: Rc<RefCell<ThreadRngInner>>, +} + +/// Retrieve the lazily-initialized thread-local random number +/// generator, seeded by the system. Intended to be used in method +/// chaining style, e.g. `thread_rng().gen::<i32>()`. +/// +/// After generating a certain amount of randomness, the RNG will reseed itself +/// from the operating system or, if the operating system RNG returns an error, +/// a seed based on the current system time. +/// +/// The internal RNG used is platform and architecture dependent, even +/// if the operating system random number generator is rigged to give +/// the same sequence always. If absolute consistency is required, +/// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`. +#[cfg(feature="std")] +pub fn thread_rng() -> ThreadRng { + // used to make space in TLS for a random number generator + thread_local!(static THREAD_RNG_KEY: Rc<RefCell<ThreadRngInner>> = { + let r = match StdRng::new() { + Ok(r) => r, + Err(e) => panic!("No entropy available: {}", e), + }; + let rng = reseeding::ReseedingRng::new(r, + THREAD_RNG_RESEED_THRESHOLD, + ThreadRngReseeder); + Rc::new(RefCell::new(rng)) + }); + + ThreadRng { rng: THREAD_RNG_KEY.with(|t| t.clone()) } +} + +#[cfg(feature="std")] +impl Rng for ThreadRng { + fn next_u32(&mut self) -> u32 { + self.rng.borrow_mut().next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.rng.borrow_mut().next_u64() + } + + #[inline] + fn fill_bytes(&mut self, bytes: &mut [u8]) { + self.rng.borrow_mut().fill_bytes(bytes) + } +} + +/// Generates a random value using the thread-local random number generator. +/// +/// `random()` can generate various types of random things, and so may require +/// type hinting to generate the specific type you want. +/// +/// This function uses the thread local random number generator. This means +/// that if you're calling `random()` in a loop, caching the generator can +/// increase performance. An example is shown below. +/// +/// # Examples +/// +/// ``` +/// let x = rand::random::<u8>(); +/// println!("{}", x); +/// +/// let y = rand::random::<f64>(); +/// println!("{}", y); +/// +/// if rand::random() { // generates a boolean +/// println!("Better lucky than good!"); +/// } +/// ``` +/// +/// Caching the thread local random number generator: +/// +/// ``` +/// use rand::Rng; +/// +/// let mut v = vec![1, 2, 3]; +/// +/// for x in v.iter_mut() { +/// *x = rand::random() +/// } +/// +/// // can be made faster by caching thread_rng +/// +/// let mut rng = rand::thread_rng(); +/// +/// for x in v.iter_mut() { +/// *x = rng.gen(); +/// } +/// ``` +#[cfg(feature="std")] +#[inline] +pub fn random<T: Rand>() -> T { + thread_rng().gen() +} + +/// DEPRECATED: use `seq::sample_iter` instead. +/// +/// Randomly sample up to `amount` elements from a finite iterator. +/// The order of elements in the sample is not random. +/// +/// # Example +/// +/// ```rust +/// use rand::{thread_rng, sample}; +/// +/// let mut rng = thread_rng(); +/// let sample = sample(&mut rng, 1..100, 5); +/// println!("{:?}", sample); +/// ``` +#[cfg(feature="std")] +#[inline(always)] +#[deprecated(since="0.4.0", note="renamed to seq::sample_iter")] +pub fn sample<T, I, R>(rng: &mut R, iterable: I, amount: usize) -> Vec<T> + where I: IntoIterator<Item=T>, + R: Rng, +{ + // the legacy sample didn't care whether amount was met + seq::sample_iter(rng, iterable, amount) + .unwrap_or_else(|e| e) +} + +#[cfg(test)] +mod test { + use super::{Rng, thread_rng, random, SeedableRng, StdRng, weak_rng}; + use std::iter::repeat; + + pub struct MyRng<R> { inner: R } + + impl<R: Rng> Rng for MyRng<R> { + fn next_u32(&mut self) -> u32 { + fn next<T: Rng>(t: &mut T) -> u32 { + t.next_u32() + } + next(&mut self.inner) + } + } + + pub fn rng() -> MyRng<::ThreadRng> { + MyRng { inner: ::thread_rng() } + } + + struct ConstRng { i: u64 } + impl Rng for ConstRng { + fn next_u32(&mut self) -> u32 { self.i as u32 } + fn next_u64(&mut self) -> u64 { self.i } + + // no fill_bytes on purpose + } + + pub fn iter_eq<I, J>(i: I, j: J) -> bool + where I: IntoIterator, + J: IntoIterator<Item=I::Item>, + I::Item: Eq + { + // make sure the iterators have equal length + let mut i = i.into_iter(); + let mut j = j.into_iter(); + loop { + match (i.next(), j.next()) { + (Some(ref ei), Some(ref ej)) if ei == ej => { } + (None, None) => return true, + _ => return false, + } + } + } + + #[test] + fn test_fill_bytes_default() { + let mut r = ConstRng { i: 0x11_22_33_44_55_66_77_88 }; + + // check every remainder mod 8, both in small and big vectors. + let lengths = [0, 1, 2, 3, 4, 5, 6, 7, + 80, 81, 82, 83, 84, 85, 86, 87]; + for &n in lengths.iter() { + let mut v = repeat(0u8).take(n).collect::<Vec<_>>(); + r.fill_bytes(&mut v); + + // use this to get nicer error messages. + for (i, &byte) in v.iter().enumerate() { + if byte == 0 { + panic!("byte {} of {} is zero", i, n) + } + } + } + } + + #[test] + fn test_gen_range() { + let mut r = thread_rng(); + for _ in 0..1000 { + let a = r.gen_range(-3, 42); + assert!(a >= -3 && a < 42); + assert_eq!(r.gen_range(0, 1), 0); + assert_eq!(r.gen_range(-12, -11), -12); + } + + for _ in 0..1000 { + let a = r.gen_range(10, 42); + assert!(a >= 10 && a < 42); + assert_eq!(r.gen_range(0, 1), 0); + assert_eq!(r.gen_range(3_000_000, 3_000_001), 3_000_000); + } + + } + + #[test] + #[should_panic] + fn test_gen_range_panic_int() { + let mut r = thread_rng(); + r.gen_range(5, -2); + } + + #[test] + #[should_panic] + fn test_gen_range_panic_usize() { + let mut r = thread_rng(); + r.gen_range(5, 2); + } + + #[test] + fn test_gen_weighted_bool() { + let mut r = thread_rng(); + assert_eq!(r.gen_weighted_bool(0), true); + assert_eq!(r.gen_weighted_bool(1), true); + } + + #[test] + fn test_gen_ascii_str() { + let mut r = thread_rng(); + assert_eq!(r.gen_ascii_chars().take(0).count(), 0); + assert_eq!(r.gen_ascii_chars().take(10).count(), 10); + assert_eq!(r.gen_ascii_chars().take(16).count(), 16); + } + + #[test] + fn test_gen_vec() { + let mut r = thread_rng(); + assert_eq!(r.gen_iter::<u8>().take(0).count(), 0); + assert_eq!(r.gen_iter::<u8>().take(10).count(), 10); + assert_eq!(r.gen_iter::<f64>().take(16).count(), 16); + } + + #[test] + fn test_choose() { + let mut r = thread_rng(); + assert_eq!(r.choose(&[1, 1, 1]).map(|&x|x), Some(1)); + + let v: &[isize] = &[]; + assert_eq!(r.choose(v), None); + } + + #[test] + fn test_shuffle() { + let mut r = thread_rng(); + let empty: &mut [isize] = &mut []; + r.shuffle(empty); + let mut one = [1]; + r.shuffle(&mut one); + let b: &[_] = &[1]; + assert_eq!(one, b); + + let mut two = [1, 2]; + r.shuffle(&mut two); + assert!(two == [1, 2] || two == [2, 1]); + + let mut x = [1, 1, 1]; + r.shuffle(&mut x); + let b: &[_] = &[1, 1, 1]; + assert_eq!(x, b); + } + + #[test] + fn test_thread_rng() { + let mut r = thread_rng(); + r.gen::<i32>(); + let mut v = [1, 1, 1]; + r.shuffle(&mut v); + let b: &[_] = &[1, 1, 1]; + assert_eq!(v, b); + assert_eq!(r.gen_range(0, 1), 0); + } + + #[test] + fn test_rng_trait_object() { + let mut rng = thread_rng(); + { + let mut r = &mut rng as &mut Rng; + r.next_u32(); + (&mut r).gen::<i32>(); + let mut v = [1, 1, 1]; + (&mut r).shuffle(&mut v); + let b: &[_] = &[1, 1, 1]; + assert_eq!(v, b); + assert_eq!((&mut r).gen_range(0, 1), 0); + } + { + let mut r = Box::new(rng) as Box<Rng>; + r.next_u32(); + r.gen::<i32>(); + let mut v = [1, 1, 1]; + r.shuffle(&mut v); + let b: &[_] = &[1, 1, 1]; + assert_eq!(v, b); + assert_eq!(r.gen_range(0, 1), 0); + } + } + + #[test] + fn test_random() { + // not sure how to test this aside from just getting some values + let _n : usize = random(); + let _f : f32 = random(); + let _o : Option<Option<i8>> = random(); + let _many : ((), + (usize, + isize, + Option<(u32, (bool,))>), + (u8, i8, u16, i16, u32, i32, u64, i64), + (f32, (f64, (f64,)))) = random(); + } + + #[test] + fn test_std_rng_seeded() { + let s = thread_rng().gen_iter::<usize>().take(256).collect::<Vec<usize>>(); + let mut ra: StdRng = SeedableRng::from_seed(&s[..]); + let mut rb: StdRng = SeedableRng::from_seed(&s[..]); + assert!(iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_std_rng_reseed() { + let s = thread_rng().gen_iter::<usize>().take(256).collect::<Vec<usize>>(); + let mut r: StdRng = SeedableRng::from_seed(&s[..]); + let string1 = r.gen_ascii_chars().take(100).collect::<String>(); + + r.reseed(&s); + + let string2 = r.gen_ascii_chars().take(100).collect::<String>(); + assert_eq!(string1, string2); + } + + #[test] + fn test_weak_rng() { + let s = weak_rng().gen_iter::<usize>().take(256).collect::<Vec<usize>>(); + let mut ra: StdRng = SeedableRng::from_seed(&s[..]); + let mut rb: StdRng = SeedableRng::from_seed(&s[..]); + assert!(iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } +} diff --git a/rand/src/os.rs b/rand/src/os.rs new file mode 100644 index 0000000..10022fb --- /dev/null +++ b/rand/src/os.rs @@ -0,0 +1,617 @@ +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Interfaces to the operating system provided random number +//! generators. + +use std::{io, mem, fmt}; +use Rng; + +/// A random number generator that retrieves randomness straight from +/// the operating system. Platform sources: +/// +/// - Unix-like systems (Linux, Android, Mac OSX): read directly from +/// `/dev/urandom`, or from `getrandom(2)` system call if available. +/// - OpenBSD: calls `getentropy(2)` +/// - FreeBSD: uses the `kern.arandom` `sysctl(2)` mib +/// - Windows: calls `RtlGenRandom`, exported from `advapi32.dll` as +/// `SystemFunction036`. +/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed. +/// - PNaCl: calls into the `nacl-irt-random-0.1` IRT interface. +/// +/// This usually does not block. On some systems (e.g. FreeBSD, OpenBSD, +/// Max OS X, and modern Linux) this may block very early in the init +/// process, if the CSPRNG has not been seeded yet.[1] +/// +/// [1] See <https://www.python.org/dev/peps/pep-0524/> for a more +/// in-depth discussion. +pub struct OsRng(imp::OsRng); + +impl OsRng { + /// Create a new `OsRng`. + pub fn new() -> io::Result<OsRng> { + imp::OsRng::new().map(OsRng) + } +} + +impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { self.0.next_u32() } + fn next_u64(&mut self) -> u64 { self.0.next_u64() } + fn fill_bytes(&mut self, v: &mut [u8]) { self.0.fill_bytes(v) } +} + +impl fmt::Debug for OsRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "OsRng {{}}") + } +} + +fn next_u32(fill_buf: &mut FnMut(&mut [u8])) -> u32 { + let mut buf: [u8; 4] = [0; 4]; + fill_buf(&mut buf); + unsafe { mem::transmute::<[u8; 4], u32>(buf) } +} + +fn next_u64(fill_buf: &mut FnMut(&mut [u8])) -> u64 { + let mut buf: [u8; 8] = [0; 8]; + fill_buf(&mut buf); + unsafe { mem::transmute::<[u8; 8], u64>(buf) } +} + +#[cfg(all(unix, not(target_os = "ios"), + not(target_os = "nacl"), + not(target_os = "freebsd"), + not(target_os = "fuchsia"), + not(target_os = "openbsd"), + not(target_os = "redox")))] +mod imp { + extern crate libc; + + use super::{next_u32, next_u64}; + use self::OsRngInner::*; + + use std::io; + use std::fs::File; + use Rng; + use read::ReadRng; + + #[cfg(all(target_os = "linux", + any(target_arch = "x86_64", + target_arch = "x86", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "powerpc")))] + fn getrandom(buf: &mut [u8]) -> libc::c_long { + extern "C" { + fn syscall(number: libc::c_long, ...) -> libc::c_long; + } + + #[cfg(target_arch = "x86_64")] + const NR_GETRANDOM: libc::c_long = 318; + #[cfg(target_arch = "x86")] + const NR_GETRANDOM: libc::c_long = 355; + #[cfg(target_arch = "arm")] + const NR_GETRANDOM: libc::c_long = 384; + #[cfg(target_arch = "aarch64")] + const NR_GETRANDOM: libc::c_long = 278; + #[cfg(target_arch = "powerpc")] + const NR_GETRANDOM: libc::c_long = 359; + + unsafe { + syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0) + } + } + + #[cfg(not(all(target_os = "linux", + any(target_arch = "x86_64", + target_arch = "x86", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "powerpc"))))] + fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 } + + fn getrandom_fill_bytes(v: &mut [u8]) { + let mut read = 0; + let len = v.len(); + while read < len { + let result = getrandom(&mut v[read..]); + if result == -1 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue + } else { + panic!("unexpected getrandom error: {}", err); + } + } else { + read += result as usize; + } + } + } + + #[cfg(all(target_os = "linux", + any(target_arch = "x86_64", + target_arch = "x86", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "powerpc")))] + fn is_getrandom_available() -> bool { + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + use std::sync::{Once, ONCE_INIT}; + + static CHECKER: Once = ONCE_INIT; + static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT; + + CHECKER.call_once(|| { + let mut buf: [u8; 0] = []; + let result = getrandom(&mut buf); + let available = if result == -1 { + let err = io::Error::last_os_error().raw_os_error(); + err != Some(libc::ENOSYS) + } else { + true + }; + AVAILABLE.store(available, Ordering::Relaxed); + }); + + AVAILABLE.load(Ordering::Relaxed) + } + + #[cfg(not(all(target_os = "linux", + any(target_arch = "x86_64", + target_arch = "x86", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "powerpc"))))] + fn is_getrandom_available() -> bool { false } + + pub struct OsRng { + inner: OsRngInner, + } + + enum OsRngInner { + OsGetrandomRng, + OsReadRng(ReadRng<File>), + } + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + if is_getrandom_available() { + return Ok(OsRng { inner: OsGetrandomRng }); + } + + let reader = try!(File::open("/dev/urandom")); + let reader_rng = ReadRng::new(reader); + + Ok(OsRng { inner: OsReadRng(reader_rng) }) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + match self.inner { + OsGetrandomRng => next_u32(&mut getrandom_fill_bytes), + OsReadRng(ref mut rng) => rng.next_u32(), + } + } + fn next_u64(&mut self) -> u64 { + match self.inner { + OsGetrandomRng => next_u64(&mut getrandom_fill_bytes), + OsReadRng(ref mut rng) => rng.next_u64(), + } + } + fn fill_bytes(&mut self, v: &mut [u8]) { + match self.inner { + OsGetrandomRng => getrandom_fill_bytes(v), + OsReadRng(ref mut rng) => rng.fill_bytes(v) + } + } + } +} + +#[cfg(target_os = "ios")] +mod imp { + extern crate libc; + + use super::{next_u32, next_u64}; + + use std::io; + use Rng; + use self::libc::{c_int, size_t}; + + #[derive(Debug)] + pub struct OsRng; + + enum SecRandom {} + + #[allow(non_upper_case_globals)] + const kSecRandomDefault: *const SecRandom = 0 as *const SecRandom; + + #[link(name = "Security", kind = "framework")] + extern { + fn SecRandomCopyBytes(rnd: *const SecRandom, + count: size_t, bytes: *mut u8) -> c_int; + } + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + Ok(OsRng) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + next_u32(&mut |v| self.fill_bytes(v)) + } + fn next_u64(&mut self) -> u64 { + next_u64(&mut |v| self.fill_bytes(v)) + } + fn fill_bytes(&mut self, v: &mut [u8]) { + let ret = unsafe { + SecRandomCopyBytes(kSecRandomDefault, v.len() as size_t, v.as_mut_ptr()) + }; + if ret == -1 { + panic!("couldn't generate random bytes: {}", io::Error::last_os_error()); + } + } + } +} + +#[cfg(target_os = "freebsd")] +mod imp { + extern crate libc; + + use std::{io, ptr}; + use Rng; + + use super::{next_u32, next_u64}; + + #[derive(Debug)] + pub struct OsRng; + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + Ok(OsRng) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + next_u32(&mut |v| self.fill_bytes(v)) + } + fn next_u64(&mut self) -> u64 { + next_u64(&mut |v| self.fill_bytes(v)) + } + fn fill_bytes(&mut self, v: &mut [u8]) { + let mib = [libc::CTL_KERN, libc::KERN_ARND]; + // kern.arandom permits a maximum buffer size of 256 bytes + for s in v.chunks_mut(256) { + let mut s_len = s.len(); + let ret = unsafe { + libc::sysctl(mib.as_ptr(), mib.len() as libc::c_uint, + s.as_mut_ptr() as *mut _, &mut s_len, + ptr::null(), 0) + }; + if ret == -1 || s_len != s.len() { + panic!("kern.arandom sysctl failed! (returned {}, s.len() {}, oldlenp {})", + ret, s.len(), s_len); + } + } + } + } +} + +#[cfg(target_os = "openbsd")] +mod imp { + extern crate libc; + + use std::io; + use Rng; + + use super::{next_u32, next_u64}; + + #[derive(Debug)] + pub struct OsRng; + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + Ok(OsRng) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + next_u32(&mut |v| self.fill_bytes(v)) + } + fn next_u64(&mut self) -> u64 { + next_u64(&mut |v| self.fill_bytes(v)) + } + fn fill_bytes(&mut self, v: &mut [u8]) { + // getentropy(2) permits a maximum buffer size of 256 bytes + for s in v.chunks_mut(256) { + let ret = unsafe { + libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len()) + }; + if ret == -1 { + let err = io::Error::last_os_error(); + panic!("getentropy failed: {}", err); + } + } + } + } +} + +#[cfg(target_os = "redox")] +mod imp { + use std::io; + use std::fs::File; + use Rng; + use read::ReadRng; + + #[derive(Debug)] + pub struct OsRng { + inner: ReadRng<File>, + } + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + let reader = try!(File::open("rand:")); + let reader_rng = ReadRng::new(reader); + + Ok(OsRng { inner: reader_rng }) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + self.inner.next_u32() + } + fn next_u64(&mut self) -> u64 { + self.inner.next_u64() + } + fn fill_bytes(&mut self, v: &mut [u8]) { + self.inner.fill_bytes(v) + } + } +} + +#[cfg(target_os = "fuchsia")] +mod imp { + extern crate fuchsia_zircon; + + use std::io; + use Rng; + + use super::{next_u32, next_u64}; + + #[derive(Debug)] + pub struct OsRng; + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + Ok(OsRng) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + next_u32(&mut |v| self.fill_bytes(v)) + } + fn next_u64(&mut self) -> u64 { + next_u64(&mut |v| self.fill_bytes(v)) + } + fn fill_bytes(&mut self, v: &mut [u8]) { + for s in v.chunks_mut(fuchsia_zircon::sys::ZX_CPRNG_DRAW_MAX_LEN) { + let mut filled = 0; + while filled < s.len() { + match fuchsia_zircon::cprng_draw(&mut s[filled..]) { + Ok(actual) => filled += actual, + Err(e) => panic!("cprng_draw failed: {:?}", e), + }; + } + } + } + } +} + +#[cfg(windows)] +mod imp { + extern crate winapi; + + use std::io; + use Rng; + + use super::{next_u32, next_u64}; + + use self::winapi::shared::minwindef::ULONG; + use self::winapi::um::ntsecapi::RtlGenRandom; + use self::winapi::um::winnt::PVOID; + + #[derive(Debug)] + pub struct OsRng; + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + Ok(OsRng) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + next_u32(&mut |v| self.fill_bytes(v)) + } + fn next_u64(&mut self) -> u64 { + next_u64(&mut |v| self.fill_bytes(v)) + } + fn fill_bytes(&mut self, v: &mut [u8]) { + // RtlGenRandom takes an ULONG (u32) for the length so we need to + // split up the buffer. + for slice in v.chunks_mut(<ULONG>::max_value() as usize) { + let ret = unsafe { + RtlGenRandom(slice.as_mut_ptr() as PVOID, slice.len() as ULONG) + }; + if ret == 0 { + panic!("couldn't generate random bytes: {}", + io::Error::last_os_error()); + } + } + } + } +} + +#[cfg(target_os = "nacl")] +mod imp { + extern crate libc; + + use std::io; + use std::mem; + use Rng; + + use super::{next_u32, next_u64}; + + #[derive(Debug)] + pub struct OsRng(extern fn(dest: *mut libc::c_void, + bytes: libc::size_t, + read: *mut libc::size_t) -> libc::c_int); + + extern { + fn nacl_interface_query(name: *const libc::c_char, + table: *mut libc::c_void, + table_size: libc::size_t) -> libc::size_t; + } + + const INTERFACE: &'static [u8] = b"nacl-irt-random-0.1\0"; + + #[repr(C)] + struct NaClIRTRandom { + get_random_bytes: Option<extern fn(dest: *mut libc::c_void, + bytes: libc::size_t, + read: *mut libc::size_t) -> libc::c_int>, + } + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + let mut iface = NaClIRTRandom { + get_random_bytes: None, + }; + let result = unsafe { + nacl_interface_query(INTERFACE.as_ptr() as *const _, + mem::transmute(&mut iface), + mem::size_of::<NaClIRTRandom>() as libc::size_t) + }; + if result != 0 { + assert!(iface.get_random_bytes.is_some()); + let result = OsRng(iface.get_random_bytes.take().unwrap()); + Ok(result) + } else { + let error = io::ErrorKind::NotFound; + let error = io::Error::new(error, "IRT random interface missing"); + Err(error) + } + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + next_u32(&mut |v| self.fill_bytes(v)) + } + fn next_u64(&mut self) -> u64 { + next_u64(&mut |v| self.fill_bytes(v)) + } + fn fill_bytes(&mut self, v: &mut [u8]) { + let mut read = 0; + loop { + let mut r: libc::size_t = 0; + let len = v.len(); + let error = (self.0)(v[read..].as_mut_ptr() as *mut _, + (len - read) as libc::size_t, + &mut r as *mut _); + assert!(error == 0, "`get_random_bytes` failed!"); + read += r as usize; + + if read >= v.len() { break; } + } + } + } +} + +#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] +mod imp { + use std::io; + use Rng; + + #[derive(Debug)] + pub struct OsRng; + + impl OsRng { + pub fn new() -> io::Result<OsRng> { + Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + } + } + + impl Rng for OsRng { + fn next_u32(&mut self) -> u32 { + panic!("Not supported") + } + } +} + +#[cfg(test)] +mod test { + use std::sync::mpsc::channel; + use Rng; + use OsRng; + use std::thread; + + #[test] + fn test_os_rng() { + let mut r = OsRng::new().unwrap(); + + r.next_u32(); + r.next_u64(); + + let mut v = [0u8; 1000]; + r.fill_bytes(&mut v); + } + + #[test] + fn test_os_rng_tasks() { + + let mut txs = vec!(); + for _ in 0..20 { + let (tx, rx) = channel(); + txs.push(tx); + + thread::spawn(move|| { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + + // deschedule to attempt to interleave things as much + // as possible (XXX: is this a good test?) + let mut r = OsRng::new().unwrap(); + thread::yield_now(); + let mut v = [0u8; 1000]; + + for _ in 0..100 { + r.next_u32(); + thread::yield_now(); + r.next_u64(); + thread::yield_now(); + r.fill_bytes(&mut v); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } + } +} diff --git a/rand/src/prng/chacha.rs b/rand/src/prng/chacha.rs new file mode 100644 index 0000000..a73e8e7 --- /dev/null +++ b/rand/src/prng/chacha.rs @@ -0,0 +1,321 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! The ChaCha random number generator. + +use core::num::Wrapping as w; +use {Rng, SeedableRng, Rand}; + +#[allow(bad_style)] +type w32 = w<u32>; + +const KEY_WORDS : usize = 8; // 8 words for the 256-bit key +const STATE_WORDS : usize = 16; +const CHACHA_ROUNDS: u32 = 20; // Cryptographically secure from 8 upwards as of this writing + +/// A random number generator that uses the ChaCha20 algorithm [1]. +/// +/// The ChaCha algorithm is widely accepted as suitable for +/// cryptographic purposes, but this implementation has not been +/// verified as such. Prefer a generator like `OsRng` that defers to +/// the operating system for cases that need high security. +/// +/// [1]: D. J. Bernstein, [*ChaCha, a variant of +/// Salsa20*](http://cr.yp.to/chacha.html) +#[derive(Copy, Clone, Debug)] +pub struct ChaChaRng { + buffer: [w32; STATE_WORDS], // Internal buffer of output + state: [w32; STATE_WORDS], // Initial state + index: usize, // Index into state +} + +static EMPTY: ChaChaRng = ChaChaRng { + buffer: [w(0); STATE_WORDS], + state: [w(0); STATE_WORDS], + index: STATE_WORDS +}; + + +macro_rules! quarter_round{ + ($a: expr, $b: expr, $c: expr, $d: expr) => {{ + $a = $a + $b; $d = $d ^ $a; $d = w($d.0.rotate_left(16)); + $c = $c + $d; $b = $b ^ $c; $b = w($b.0.rotate_left(12)); + $a = $a + $b; $d = $d ^ $a; $d = w($d.0.rotate_left( 8)); + $c = $c + $d; $b = $b ^ $c; $b = w($b.0.rotate_left( 7)); + }} +} + +macro_rules! double_round{ + ($x: expr) => {{ + // Column round + quarter_round!($x[ 0], $x[ 4], $x[ 8], $x[12]); + quarter_round!($x[ 1], $x[ 5], $x[ 9], $x[13]); + quarter_round!($x[ 2], $x[ 6], $x[10], $x[14]); + quarter_round!($x[ 3], $x[ 7], $x[11], $x[15]); + // Diagonal round + quarter_round!($x[ 0], $x[ 5], $x[10], $x[15]); + quarter_round!($x[ 1], $x[ 6], $x[11], $x[12]); + quarter_round!($x[ 2], $x[ 7], $x[ 8], $x[13]); + quarter_round!($x[ 3], $x[ 4], $x[ 9], $x[14]); + }} +} + +#[inline] +fn core(output: &mut [w32; STATE_WORDS], input: &[w32; STATE_WORDS]) { + *output = *input; + + for _ in 0..CHACHA_ROUNDS / 2 { + double_round!(output); + } + + for i in 0..STATE_WORDS { + output[i] = output[i] + input[i]; + } +} + +impl ChaChaRng { + + /// Create an ChaCha random number generator using the default + /// fixed key of 8 zero words. + /// + /// # Examples + /// + /// ```rust + /// use rand::{Rng, ChaChaRng}; + /// + /// let mut ra = ChaChaRng::new_unseeded(); + /// println!("{:?}", ra.next_u32()); + /// println!("{:?}", ra.next_u32()); + /// ``` + /// + /// Since this equivalent to a RNG with a fixed seed, repeated executions + /// of an unseeded RNG will produce the same result. This code sample will + /// consistently produce: + /// + /// - 2917185654 + /// - 2419978656 + pub fn new_unseeded() -> ChaChaRng { + let mut rng = EMPTY; + rng.init(&[0; KEY_WORDS]); + rng + } + + /// Sets the internal 128-bit ChaCha counter to + /// a user-provided value. This permits jumping + /// arbitrarily ahead (or backwards) in the pseudorandom stream. + /// + /// Since the nonce words are used to extend the counter to 128 bits, + /// users wishing to obtain the conventional ChaCha pseudorandom stream + /// associated with a particular nonce can call this function with + /// arguments `0, desired_nonce`. + /// + /// # Examples + /// + /// ```rust + /// use rand::{Rng, ChaChaRng}; + /// + /// let mut ra = ChaChaRng::new_unseeded(); + /// ra.set_counter(0u64, 1234567890u64); + /// println!("{:?}", ra.next_u32()); + /// println!("{:?}", ra.next_u32()); + /// ``` + pub fn set_counter(&mut self, counter_low: u64, counter_high: u64) { + self.state[12] = w((counter_low >> 0) as u32); + self.state[13] = w((counter_low >> 32) as u32); + self.state[14] = w((counter_high >> 0) as u32); + self.state[15] = w((counter_high >> 32) as u32); + self.index = STATE_WORDS; // force recomputation + } + + /// Initializes `self.state` with the appropriate key and constants + /// + /// We deviate slightly from the ChaCha specification regarding + /// the nonce, which is used to extend the counter to 128 bits. + /// This is provably as strong as the original cipher, though, + /// since any distinguishing attack on our variant also works + /// against ChaCha with a chosen-nonce. See the XSalsa20 [1] + /// security proof for a more involved example of this. + /// + /// The modified word layout is: + /// ```text + /// constant constant constant constant + /// key key key key + /// key key key key + /// counter counter counter counter + /// ``` + /// [1]: Daniel J. Bernstein. [*Extending the Salsa20 + /// nonce.*](http://cr.yp.to/papers.html#xsalsa) + fn init(&mut self, key: &[u32; KEY_WORDS]) { + self.state[0] = w(0x61707865); + self.state[1] = w(0x3320646E); + self.state[2] = w(0x79622D32); + self.state[3] = w(0x6B206574); + + for i in 0..KEY_WORDS { + self.state[4+i] = w(key[i]); + } + + self.state[12] = w(0); + self.state[13] = w(0); + self.state[14] = w(0); + self.state[15] = w(0); + + self.index = STATE_WORDS; + } + + /// Refill the internal output buffer (`self.buffer`) + fn update(&mut self) { + core(&mut self.buffer, &self.state); + self.index = 0; + // update 128-bit counter + self.state[12] = self.state[12] + w(1); + if self.state[12] != w(0) { return }; + self.state[13] = self.state[13] + w(1); + if self.state[13] != w(0) { return }; + self.state[14] = self.state[14] + w(1); + if self.state[14] != w(0) { return }; + self.state[15] = self.state[15] + w(1); + } +} + +impl Rng for ChaChaRng { + #[inline] + fn next_u32(&mut self) -> u32 { + if self.index == STATE_WORDS { + self.update(); + } + + let value = self.buffer[self.index % STATE_WORDS]; + self.index += 1; + value.0 + } +} + +impl<'a> SeedableRng<&'a [u32]> for ChaChaRng { + + fn reseed(&mut self, seed: &'a [u32]) { + // reset state + self.init(&[0u32; KEY_WORDS]); + // set key in place + let key = &mut self.state[4 .. 4+KEY_WORDS]; + for (k, s) in key.iter_mut().zip(seed.iter()) { + *k = w(*s); + } + } + + /// Create a ChaCha generator from a seed, + /// obtained from a variable-length u32 array. + /// Only up to 8 words are used; if less than 8 + /// words are used, the remaining are set to zero. + fn from_seed(seed: &'a [u32]) -> ChaChaRng { + let mut rng = EMPTY; + rng.reseed(seed); + rng + } +} + +impl Rand for ChaChaRng { + fn rand<R: Rng>(other: &mut R) -> ChaChaRng { + let mut key : [u32; KEY_WORDS] = [0; KEY_WORDS]; + for word in key.iter_mut() { + *word = other.gen(); + } + SeedableRng::from_seed(&key[..]) + } +} + + +#[cfg(test)] +mod test { + use {Rng, SeedableRng}; + use super::ChaChaRng; + + #[test] + fn test_rng_rand_seeded() { + let s = ::test::rng().gen_iter::<u32>().take(8).collect::<Vec<u32>>(); + let mut ra: ChaChaRng = SeedableRng::from_seed(&s[..]); + let mut rb: ChaChaRng = SeedableRng::from_seed(&s[..]); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_seeded() { + let seed : &[_] = &[0,1,2,3,4,5,6,7]; + let mut ra: ChaChaRng = SeedableRng::from_seed(seed); + let mut rb: ChaChaRng = SeedableRng::from_seed(seed); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_reseed() { + let s = ::test::rng().gen_iter::<u32>().take(8).collect::<Vec<u32>>(); + let mut r: ChaChaRng = SeedableRng::from_seed(&s[..]); + let string1: String = r.gen_ascii_chars().take(100).collect(); + + r.reseed(&s); + + let string2: String = r.gen_ascii_chars().take(100).collect(); + assert_eq!(string1, string2); + } + + #[test] + fn test_rng_true_values() { + // Test vectors 1 and 2 from + // http://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed : &[_] = &[0u32; 8]; + let mut ra: ChaChaRng = SeedableRng::from_seed(seed); + + let v = (0..16).map(|_| ra.next_u32()).collect::<Vec<_>>(); + assert_eq!(v, + vec!(0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, + 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, + 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, + 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2)); + + let v = (0..16).map(|_| ra.next_u32()).collect::<Vec<_>>(); + assert_eq!(v, + vec!(0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, + 0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32, + 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, + 0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b)); + + + let seed : &[_] = &[0,1,2,3,4,5,6,7]; + let mut ra: ChaChaRng = SeedableRng::from_seed(seed); + + // Store the 17*i-th 32-bit word, + // i.e., the i-th word of the i-th 16-word block + let mut v : Vec<u32> = Vec::new(); + for _ in 0..16 { + v.push(ra.next_u32()); + for _ in 0..16 { + ra.next_u32(); + } + } + + assert_eq!(v, + vec!(0xf225c81a, 0x6ab1be57, 0x04d42951, 0x70858036, + 0x49884684, 0x64efec72, 0x4be2d186, 0x3615b384, + 0x11cfa18e, 0xd3c50049, 0x75c775f6, 0x434c6530, + 0x2c5bad8f, 0x898881dc, 0x5f1c86d9, 0xc1f8e7f4)); + } + + #[test] + fn test_rng_clone() { + let seed : &[_] = &[0u32; 8]; + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut clone = rng.clone(); + for _ in 0..16 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } +} diff --git a/rand/src/prng/isaac.rs b/rand/src/prng/isaac.rs new file mode 100644 index 0000000..cf5eb67 --- /dev/null +++ b/rand/src/prng/isaac.rs @@ -0,0 +1,328 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! The ISAAC random number generator. + +#![allow(non_camel_case_types)] + +use core::slice; +use core::iter::repeat; +use core::num::Wrapping as w; +use core::fmt; + +use {Rng, SeedableRng, Rand}; + +#[allow(bad_style)] +type w32 = w<u32>; + +const RAND_SIZE_LEN: usize = 8; +const RAND_SIZE: u32 = 1 << RAND_SIZE_LEN; +const RAND_SIZE_USIZE: usize = 1 << RAND_SIZE_LEN; + +/// A random number generator that uses the ISAAC algorithm[1]. +/// +/// The ISAAC algorithm is generally accepted as suitable for +/// cryptographic purposes, but this implementation has not be +/// verified as such. Prefer a generator like `OsRng` that defers to +/// the operating system for cases that need high security. +/// +/// [1]: Bob Jenkins, [*ISAAC: A fast cryptographic random number +/// generator*](http://www.burtleburtle.net/bob/rand/isaacafa.html) +#[derive(Copy)] +pub struct IsaacRng { + cnt: u32, + rsl: [w32; RAND_SIZE_USIZE], + mem: [w32; RAND_SIZE_USIZE], + a: w32, + b: w32, + c: w32, +} + +static EMPTY: IsaacRng = IsaacRng { + cnt: 0, + rsl: [w(0); RAND_SIZE_USIZE], + mem: [w(0); RAND_SIZE_USIZE], + a: w(0), b: w(0), c: w(0), +}; + +impl IsaacRng { + + /// Create an ISAAC random number generator using the default + /// fixed seed. + pub fn new_unseeded() -> IsaacRng { + let mut rng = EMPTY; + rng.init(false); + rng + } + + /// Initialises `self`. If `use_rsl` is true, then use the current value + /// of `rsl` as a seed, otherwise construct one algorithmically (not + /// randomly). + fn init(&mut self, use_rsl: bool) { + let mut a = w(0x9e3779b9); + let mut b = a; + let mut c = a; + let mut d = a; + let mut e = a; + let mut f = a; + let mut g = a; + let mut h = a; + + macro_rules! mix { + () => {{ + a=a^(b<<11); d=d+a; b=b+c; + b=b^(c>>2); e=e+b; c=c+d; + c=c^(d<<8); f=f+c; d=d+e; + d=d^(e>>16); g=g+d; e=e+f; + e=e^(f<<10); h=h+e; f=f+g; + f=f^(g>>4); a=a+f; g=g+h; + g=g^(h<<8); b=b+g; h=h+a; + h=h^(a>>9); c=c+h; a=a+b; + }} + } + + for _ in 0..4 { + mix!(); + } + + if use_rsl { + macro_rules! memloop { + ($arr:expr) => {{ + for i in (0..RAND_SIZE_USIZE/8).map(|i| i * 8) { + a=a+$arr[i ]; b=b+$arr[i+1]; + c=c+$arr[i+2]; d=d+$arr[i+3]; + e=e+$arr[i+4]; f=f+$arr[i+5]; + g=g+$arr[i+6]; h=h+$arr[i+7]; + mix!(); + self.mem[i ]=a; self.mem[i+1]=b; + self.mem[i+2]=c; self.mem[i+3]=d; + self.mem[i+4]=e; self.mem[i+5]=f; + self.mem[i+6]=g; self.mem[i+7]=h; + } + }} + } + + memloop!(self.rsl); + memloop!(self.mem); + } else { + for i in (0..RAND_SIZE_USIZE/8).map(|i| i * 8) { + mix!(); + self.mem[i ]=a; self.mem[i+1]=b; + self.mem[i+2]=c; self.mem[i+3]=d; + self.mem[i+4]=e; self.mem[i+5]=f; + self.mem[i+6]=g; self.mem[i+7]=h; + } + } + + self.isaac(); + } + + /// Refills the output buffer (`self.rsl`) + #[inline] + fn isaac(&mut self) { + self.c = self.c + w(1); + // abbreviations + let mut a = self.a; + let mut b = self.b + self.c; + + const MIDPOINT: usize = RAND_SIZE_USIZE / 2; + + macro_rules! ind { + ($x:expr) => ( self.mem[($x >> 2usize).0 as usize & (RAND_SIZE_USIZE - 1)] ) + } + + let r = [(0, MIDPOINT), (MIDPOINT, 0)]; + for &(mr_offset, m2_offset) in r.iter() { + + macro_rules! rngstepp { + ($j:expr, $shift:expr) => {{ + let base = $j; + let mix = a << $shift; + + let x = self.mem[base + mr_offset]; + a = (a ^ mix) + self.mem[base + m2_offset]; + let y = ind!(x) + a + b; + self.mem[base + mr_offset] = y; + + b = ind!(y >> RAND_SIZE_LEN) + x; + self.rsl[base + mr_offset] = b; + }} + } + + macro_rules! rngstepn { + ($j:expr, $shift:expr) => {{ + let base = $j; + let mix = a >> $shift; + + let x = self.mem[base + mr_offset]; + a = (a ^ mix) + self.mem[base + m2_offset]; + let y = ind!(x) + a + b; + self.mem[base + mr_offset] = y; + + b = ind!(y >> RAND_SIZE_LEN) + x; + self.rsl[base + mr_offset] = b; + }} + } + + for i in (0..MIDPOINT/4).map(|i| i * 4) { + rngstepp!(i + 0, 13); + rngstepn!(i + 1, 6); + rngstepp!(i + 2, 2); + rngstepn!(i + 3, 16); + } + } + + self.a = a; + self.b = b; + self.cnt = RAND_SIZE; + } +} + +// Cannot be derived because [u32; 256] does not implement Clone +impl Clone for IsaacRng { + fn clone(&self) -> IsaacRng { + *self + } +} + +impl Rng for IsaacRng { + #[inline] + fn next_u32(&mut self) -> u32 { + if self.cnt == 0 { + // make some more numbers + self.isaac(); + } + self.cnt -= 1; + + // self.cnt is at most RAND_SIZE, but that is before the + // subtraction above. We want to index without bounds + // checking, but this could lead to incorrect code if someone + // misrefactors, so we check, sometimes. + // + // (Changes here should be reflected in Isaac64Rng.next_u64.) + debug_assert!(self.cnt < RAND_SIZE); + + // (the % is cheaply telling the optimiser that we're always + // in bounds, without unsafe. NB. this is a power of two, so + // it optimises to a bitwise mask). + self.rsl[(self.cnt % RAND_SIZE) as usize].0 + } +} + +impl<'a> SeedableRng<&'a [u32]> for IsaacRng { + fn reseed(&mut self, seed: &'a [u32]) { + // make the seed into [seed[0], seed[1], ..., seed[seed.len() + // - 1], 0, 0, ...], to fill rng.rsl. + let seed_iter = seed.iter().map(|&x| x).chain(repeat(0u32)); + + for (rsl_elem, seed_elem) in self.rsl.iter_mut().zip(seed_iter) { + *rsl_elem = w(seed_elem); + } + self.cnt = 0; + self.a = w(0); + self.b = w(0); + self.c = w(0); + + self.init(true); + } + + /// Create an ISAAC random number generator with a seed. This can + /// be any length, although the maximum number of elements used is + /// 256 and any more will be silently ignored. A generator + /// constructed with a given seed will generate the same sequence + /// of values as all other generators constructed with that seed. + fn from_seed(seed: &'a [u32]) -> IsaacRng { + let mut rng = EMPTY; + rng.reseed(seed); + rng + } +} + +impl Rand for IsaacRng { + fn rand<R: Rng>(other: &mut R) -> IsaacRng { + let mut ret = EMPTY; + unsafe { + let ptr = ret.rsl.as_mut_ptr() as *mut u8; + + let slice = slice::from_raw_parts_mut(ptr, RAND_SIZE_USIZE * 4); + other.fill_bytes(slice); + } + ret.cnt = 0; + ret.a = w(0); + ret.b = w(0); + ret.c = w(0); + + ret.init(true); + return ret; + } +} + +impl fmt::Debug for IsaacRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "IsaacRng {{}}") + } +} + +#[cfg(test)] +mod test { + use {Rng, SeedableRng}; + use super::IsaacRng; + + #[test] + fn test_rng_32_rand_seeded() { + let s = ::test::rng().gen_iter::<u32>().take(256).collect::<Vec<u32>>(); + let mut ra: IsaacRng = SeedableRng::from_seed(&s[..]); + let mut rb: IsaacRng = SeedableRng::from_seed(&s[..]); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_32_seeded() { + let seed: &[_] = &[1, 23, 456, 7890, 12345]; + let mut ra: IsaacRng = SeedableRng::from_seed(seed); + let mut rb: IsaacRng = SeedableRng::from_seed(seed); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_32_reseed() { + let s = ::test::rng().gen_iter::<u32>().take(256).collect::<Vec<u32>>(); + let mut r: IsaacRng = SeedableRng::from_seed(&s[..]); + let string1: String = r.gen_ascii_chars().take(100).collect(); + + r.reseed(&s[..]); + + let string2: String = r.gen_ascii_chars().take(100).collect(); + assert_eq!(string1, string2); + } + + #[test] + fn test_rng_32_true_values() { + let seed: &[_] = &[1, 23, 456, 7890, 12345]; + let mut ra: IsaacRng = SeedableRng::from_seed(seed); + // Regression test that isaac is actually using the above vector + let v = (0..10).map(|_| ra.next_u32()).collect::<Vec<_>>(); + assert_eq!(v, + vec!(2558573138, 873787463, 263499565, 2103644246, 3595684709, + 4203127393, 264982119, 2765226902, 2737944514, 3900253796)); + + let seed: &[_] = &[12345, 67890, 54321, 9876]; + let mut rb: IsaacRng = SeedableRng::from_seed(seed); + // skip forward to the 10000th number + for _ in 0..10000 { rb.next_u32(); } + + let v = (0..10).map(|_| rb.next_u32()).collect::<Vec<_>>(); + assert_eq!(v, + vec!(3676831399, 3183332890, 2834741178, 3854698763, 2717568474, + 1576568959, 3507990155, 179069555, 141456972, 2478885421)); + } +} diff --git a/rand/src/prng/isaac64.rs b/rand/src/prng/isaac64.rs new file mode 100644 index 0000000..b98e3fe --- /dev/null +++ b/rand/src/prng/isaac64.rs @@ -0,0 +1,340 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! The ISAAC-64 random number generator. + +use core::slice; +use core::iter::repeat; +use core::num::Wrapping as w; +use core::fmt; + +use {Rng, SeedableRng, Rand}; + +#[allow(bad_style)] +type w64 = w<u64>; + +const RAND_SIZE_64_LEN: usize = 8; +const RAND_SIZE_64: usize = 1 << RAND_SIZE_64_LEN; + +/// A random number generator that uses ISAAC-64[1], the 64-bit +/// variant of the ISAAC algorithm. +/// +/// The ISAAC algorithm is generally accepted as suitable for +/// cryptographic purposes, but this implementation has not be +/// verified as such. Prefer a generator like `OsRng` that defers to +/// the operating system for cases that need high security. +/// +/// [1]: Bob Jenkins, [*ISAAC: A fast cryptographic random number +/// generator*](http://www.burtleburtle.net/bob/rand/isaacafa.html) +#[derive(Copy)] +pub struct Isaac64Rng { + cnt: usize, + rsl: [w64; RAND_SIZE_64], + mem: [w64; RAND_SIZE_64], + a: w64, + b: w64, + c: w64, +} + +static EMPTY_64: Isaac64Rng = Isaac64Rng { + cnt: 0, + rsl: [w(0); RAND_SIZE_64], + mem: [w(0); RAND_SIZE_64], + a: w(0), b: w(0), c: w(0), +}; + +impl Isaac64Rng { + /// Create a 64-bit ISAAC random number generator using the + /// default fixed seed. + pub fn new_unseeded() -> Isaac64Rng { + let mut rng = EMPTY_64; + rng.init(false); + rng + } + + /// Initialises `self`. If `use_rsl` is true, then use the current value + /// of `rsl` as a seed, otherwise construct one algorithmically (not + /// randomly). + fn init(&mut self, use_rsl: bool) { + macro_rules! init { + ($var:ident) => ( + let mut $var = w(0x9e3779b97f4a7c13); + ) + } + init!(a); init!(b); init!(c); init!(d); + init!(e); init!(f); init!(g); init!(h); + + macro_rules! mix { + () => {{ + a=a-e; f=f^(h>>9); h=h+a; + b=b-f; g=g^(a<<9); a=a+b; + c=c-g; h=h^(b>>23); b=b+c; + d=d-h; a=a^(c<<15); c=c+d; + e=e-a; b=b^(d>>14); d=d+e; + f=f-b; c=c^(e<<20); e=e+f; + g=g-c; d=d^(f>>17); f=f+g; + h=h-d; e=e^(g<<14); g=g+h; + }} + } + + for _ in 0..4 { + mix!(); + } + + if use_rsl { + macro_rules! memloop { + ($arr:expr) => {{ + for i in (0..RAND_SIZE_64 / 8).map(|i| i * 8) { + a=a+$arr[i ]; b=b+$arr[i+1]; + c=c+$arr[i+2]; d=d+$arr[i+3]; + e=e+$arr[i+4]; f=f+$arr[i+5]; + g=g+$arr[i+6]; h=h+$arr[i+7]; + mix!(); + self.mem[i ]=a; self.mem[i+1]=b; + self.mem[i+2]=c; self.mem[i+3]=d; + self.mem[i+4]=e; self.mem[i+5]=f; + self.mem[i+6]=g; self.mem[i+7]=h; + } + }} + } + + memloop!(self.rsl); + memloop!(self.mem); + } else { + for i in (0..RAND_SIZE_64 / 8).map(|i| i * 8) { + mix!(); + self.mem[i ]=a; self.mem[i+1]=b; + self.mem[i+2]=c; self.mem[i+3]=d; + self.mem[i+4]=e; self.mem[i+5]=f; + self.mem[i+6]=g; self.mem[i+7]=h; + } + } + + self.isaac64(); + } + + /// Refills the output buffer (`self.rsl`) + fn isaac64(&mut self) { + self.c = self.c + w(1); + // abbreviations + let mut a = self.a; + let mut b = self.b + self.c; + const MIDPOINT: usize = RAND_SIZE_64 / 2; + const MP_VEC: [(usize, usize); 2] = [(0,MIDPOINT), (MIDPOINT, 0)]; + macro_rules! ind { + ($x:expr) => { + *self.mem.get_unchecked((($x >> 3usize).0 as usize) & (RAND_SIZE_64 - 1)) + } + } + + for &(mr_offset, m2_offset) in MP_VEC.iter() { + for base in (0..MIDPOINT / 4).map(|i| i * 4) { + + macro_rules! rngstepp { + ($j:expr, $shift:expr) => {{ + let base = base + $j; + let mix = a ^ (a << $shift); + let mix = if $j == 0 {!mix} else {mix}; + + unsafe { + let x = *self.mem.get_unchecked(base + mr_offset); + a = mix + *self.mem.get_unchecked(base + m2_offset); + let y = ind!(x) + a + b; + *self.mem.get_unchecked_mut(base + mr_offset) = y; + + b = ind!(y >> RAND_SIZE_64_LEN) + x; + *self.rsl.get_unchecked_mut(base + mr_offset) = b; + } + }} + } + + macro_rules! rngstepn { + ($j:expr, $shift:expr) => {{ + let base = base + $j; + let mix = a ^ (a >> $shift); + let mix = if $j == 0 {!mix} else {mix}; + + unsafe { + let x = *self.mem.get_unchecked(base + mr_offset); + a = mix + *self.mem.get_unchecked(base + m2_offset); + let y = ind!(x) + a + b; + *self.mem.get_unchecked_mut(base + mr_offset) = y; + + b = ind!(y >> RAND_SIZE_64_LEN) + x; + *self.rsl.get_unchecked_mut(base + mr_offset) = b; + } + }} + } + + rngstepp!(0, 21); + rngstepn!(1, 5); + rngstepp!(2, 12); + rngstepn!(3, 33); + } + } + + self.a = a; + self.b = b; + self.cnt = RAND_SIZE_64; + } +} + +// Cannot be derived because [u32; 256] does not implement Clone +impl Clone for Isaac64Rng { + fn clone(&self) -> Isaac64Rng { + *self + } +} + +impl Rng for Isaac64Rng { + #[inline] + fn next_u32(&mut self) -> u32 { + self.next_u64() as u32 + } + + #[inline] + fn next_u64(&mut self) -> u64 { + if self.cnt == 0 { + // make some more numbers + self.isaac64(); + } + self.cnt -= 1; + + // See corresponding location in IsaacRng.next_u32 for + // explanation. + debug_assert!(self.cnt < RAND_SIZE_64); + self.rsl[(self.cnt % RAND_SIZE_64) as usize].0 + } +} + +impl<'a> SeedableRng<&'a [u64]> for Isaac64Rng { + fn reseed(&mut self, seed: &'a [u64]) { + // make the seed into [seed[0], seed[1], ..., seed[seed.len() + // - 1], 0, 0, ...], to fill rng.rsl. + let seed_iter = seed.iter().map(|&x| x).chain(repeat(0u64)); + + for (rsl_elem, seed_elem) in self.rsl.iter_mut().zip(seed_iter) { + *rsl_elem = w(seed_elem); + } + self.cnt = 0; + self.a = w(0); + self.b = w(0); + self.c = w(0); + + self.init(true); + } + + /// Create an ISAAC random number generator with a seed. This can + /// be any length, although the maximum number of elements used is + /// 256 and any more will be silently ignored. A generator + /// constructed with a given seed will generate the same sequence + /// of values as all other generators constructed with that seed. + fn from_seed(seed: &'a [u64]) -> Isaac64Rng { + let mut rng = EMPTY_64; + rng.reseed(seed); + rng + } +} + +impl Rand for Isaac64Rng { + fn rand<R: Rng>(other: &mut R) -> Isaac64Rng { + let mut ret = EMPTY_64; + unsafe { + let ptr = ret.rsl.as_mut_ptr() as *mut u8; + + let slice = slice::from_raw_parts_mut(ptr, RAND_SIZE_64 * 8); + other.fill_bytes(slice); + } + ret.cnt = 0; + ret.a = w(0); + ret.b = w(0); + ret.c = w(0); + + ret.init(true); + return ret; + } +} + +impl fmt::Debug for Isaac64Rng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Isaac64Rng {{}}") + } +} + +#[cfg(test)] +mod test { + use {Rng, SeedableRng}; + use super::Isaac64Rng; + + #[test] + fn test_rng_64_rand_seeded() { + let s = ::test::rng().gen_iter::<u64>().take(256).collect::<Vec<u64>>(); + let mut ra: Isaac64Rng = SeedableRng::from_seed(&s[..]); + let mut rb: Isaac64Rng = SeedableRng::from_seed(&s[..]); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_64_seeded() { + let seed: &[_] = &[1, 23, 456, 7890, 12345]; + let mut ra: Isaac64Rng = SeedableRng::from_seed(seed); + let mut rb: Isaac64Rng = SeedableRng::from_seed(seed); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_64_reseed() { + let s = ::test::rng().gen_iter::<u64>().take(256).collect::<Vec<u64>>(); + let mut r: Isaac64Rng = SeedableRng::from_seed(&s[..]); + let string1: String = r.gen_ascii_chars().take(100).collect(); + + r.reseed(&s[..]); + + let string2: String = r.gen_ascii_chars().take(100).collect(); + assert_eq!(string1, string2); + } + + #[test] + fn test_rng_64_true_values() { + let seed: &[_] = &[1, 23, 456, 7890, 12345]; + let mut ra: Isaac64Rng = SeedableRng::from_seed(seed); + // Regression test that isaac is actually using the above vector + let v = (0..10).map(|_| ra.next_u64()).collect::<Vec<_>>(); + assert_eq!(v, + vec!(547121783600835980, 14377643087320773276, 17351601304698403469, + 1238879483818134882, 11952566807690396487, 13970131091560099343, + 4469761996653280935, 15552757044682284409, 6860251611068737823, + 13722198873481261842)); + + let seed: &[_] = &[12345, 67890, 54321, 9876]; + let mut rb: Isaac64Rng = SeedableRng::from_seed(seed); + // skip forward to the 10000th number + for _ in 0..10000 { rb.next_u64(); } + + let v = (0..10).map(|_| rb.next_u64()).collect::<Vec<_>>(); + assert_eq!(v, + vec!(18143823860592706164, 8491801882678285927, 2699425367717515619, + 17196852593171130876, 2606123525235546165, 15790932315217671084, + 596345674630742204, 9947027391921273664, 11788097613744130851, + 10391409374914919106)); + } + + #[test] + fn test_rng_clone() { + let seed: &[_] = &[1, 23, 456, 7890, 12345]; + let mut rng: Isaac64Rng = SeedableRng::from_seed(seed); + let mut clone = rng.clone(); + for _ in 0..16 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } +} diff --git a/rand/src/prng/mod.rs b/rand/src/prng/mod.rs new file mode 100644 index 0000000..ed3e018 --- /dev/null +++ b/rand/src/prng/mod.rs @@ -0,0 +1,51 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Pseudo random number generators are algorithms to produce *apparently +//! random* numbers deterministically, and usually fairly quickly. +//! +//! So long as the algorithm is computationally secure, is initialised with +//! sufficient entropy (i.e. unknown by an attacker), and its internal state is +//! also protected (unknown to an attacker), the output will also be +//! *computationally secure*. Computationally Secure Pseudo Random Number +//! Generators (CSPRNGs) are thus suitable sources of random numbers for +//! cryptography. There are a couple of gotchas here, however. First, the seed +//! used for initialisation must be unknown. Usually this should be provided by +//! the operating system and should usually be secure, however this may not +//! always be the case (especially soon after startup). Second, user-space +//! memory may be vulnerable, for example when written to swap space, and after +//! forking a child process should reinitialise any user-space PRNGs. For this +//! reason it may be preferable to source random numbers directly from the OS +//! for cryptographic applications. +//! +//! PRNGs are also widely used for non-cryptographic uses: randomised +//! algorithms, simulations, games. In these applications it is usually not +//! important for numbers to be cryptographically *unguessable*, but even +//! distribution and independence from other samples (from the point of view +//! of someone unaware of the algorithm used, at least) may still be important. +//! Good PRNGs should satisfy these properties, but do not take them for +//! granted; Wikipedia's article on +//! [Pseudorandom number generators](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) +//! provides some background on this topic. +//! +//! Care should be taken when seeding (initialising) PRNGs. Some PRNGs have +//! short periods for some seeds. If one PRNG is seeded from another using the +//! same algorithm, it is possible that both will yield the same sequence of +//! values (with some lag). + +mod chacha; +mod isaac; +mod isaac64; +mod xorshift; + +pub use self::chacha::ChaChaRng; +pub use self::isaac::IsaacRng; +pub use self::isaac64::Isaac64Rng; +pub use self::xorshift::XorShiftRng; diff --git a/rand/src/prng/xorshift.rs b/rand/src/prng/xorshift.rs new file mode 100644 index 0000000..dd367e9 --- /dev/null +++ b/rand/src/prng/xorshift.rs @@ -0,0 +1,101 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Xorshift generators + +use core::num::Wrapping as w; +use {Rng, SeedableRng, Rand}; + +/// An Xorshift[1] random number +/// generator. +/// +/// The Xorshift algorithm is not suitable for cryptographic purposes +/// but is very fast. If you do not know for sure that it fits your +/// requirements, use a more secure one such as `IsaacRng` or `OsRng`. +/// +/// [1]: Marsaglia, George (July 2003). ["Xorshift +/// RNGs"](http://www.jstatsoft.org/v08/i14/paper). *Journal of +/// Statistical Software*. Vol. 8 (Issue 14). +#[allow(missing_copy_implementations)] +#[derive(Clone, Debug)] +pub struct XorShiftRng { + x: w<u32>, + y: w<u32>, + z: w<u32>, + w: w<u32>, +} + +impl XorShiftRng { + /// Creates a new XorShiftRng instance which is not seeded. + /// + /// The initial values of this RNG are constants, so all generators created + /// by this function will yield the same stream of random numbers. It is + /// highly recommended that this is created through `SeedableRng` instead of + /// this function + pub fn new_unseeded() -> XorShiftRng { + XorShiftRng { + x: w(0x193a6754), + y: w(0xa8a7d469), + z: w(0x97830e05), + w: w(0x113ba7bb), + } + } +} + +impl Rng for XorShiftRng { + #[inline] + fn next_u32(&mut self) -> u32 { + let x = self.x; + let t = x ^ (x << 11); + self.x = self.y; + self.y = self.z; + self.z = self.w; + let w_ = self.w; + self.w = w_ ^ (w_ >> 19) ^ (t ^ (t >> 8)); + self.w.0 + } +} + +impl SeedableRng<[u32; 4]> for XorShiftRng { + /// Reseed an XorShiftRng. This will panic if `seed` is entirely 0. + fn reseed(&mut self, seed: [u32; 4]) { + assert!(!seed.iter().all(|&x| x == 0), + "XorShiftRng.reseed called with an all zero seed."); + + self.x = w(seed[0]); + self.y = w(seed[1]); + self.z = w(seed[2]); + self.w = w(seed[3]); + } + + /// Create a new XorShiftRng. This will panic if `seed` is entirely 0. + fn from_seed(seed: [u32; 4]) -> XorShiftRng { + assert!(!seed.iter().all(|&x| x == 0), + "XorShiftRng::from_seed called with an all zero seed."); + + XorShiftRng { + x: w(seed[0]), + y: w(seed[1]), + z: w(seed[2]), + w: w(seed[3]), + } + } +} + +impl Rand for XorShiftRng { + fn rand<R: Rng>(rng: &mut R) -> XorShiftRng { + let mut tuple: (u32, u32, u32, u32) = rng.gen(); + while tuple == (0, 0, 0, 0) { + tuple = rng.gen(); + } + let (x, y, z, w_) = tuple; + XorShiftRng { x: w(x), y: w(y), z: w(z), w: w(w_) } + } +} diff --git a/rand/src/rand_impls.rs b/rand/src/rand_impls.rs new file mode 100644 index 0000000..a865bb6 --- /dev/null +++ b/rand/src/rand_impls.rs @@ -0,0 +1,299 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! The implementations of `Rand` for the built-in types. + +use core::{char, mem}; + +use {Rand,Rng}; + +impl Rand for isize { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> isize { + if mem::size_of::<isize>() == 4 { + rng.gen::<i32>() as isize + } else { + rng.gen::<i64>() as isize + } + } +} + +impl Rand for i8 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> i8 { + rng.next_u32() as i8 + } +} + +impl Rand for i16 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> i16 { + rng.next_u32() as i16 + } +} + +impl Rand for i32 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> i32 { + rng.next_u32() as i32 + } +} + +impl Rand for i64 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> i64 { + rng.next_u64() as i64 + } +} + +#[cfg(feature = "i128_support")] +impl Rand for i128 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> i128 { + rng.gen::<u128>() as i128 + } +} + +impl Rand for usize { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> usize { + if mem::size_of::<usize>() == 4 { + rng.gen::<u32>() as usize + } else { + rng.gen::<u64>() as usize + } + } +} + +impl Rand for u8 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> u8 { + rng.next_u32() as u8 + } +} + +impl Rand for u16 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> u16 { + rng.next_u32() as u16 + } +} + +impl Rand for u32 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> u32 { + rng.next_u32() + } +} + +impl Rand for u64 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> u64 { + rng.next_u64() + } +} + +#[cfg(feature = "i128_support")] +impl Rand for u128 { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> u128 { + ((rng.next_u64() as u128) << 64) | (rng.next_u64() as u128) + } +} + + +macro_rules! float_impls { + ($mod_name:ident, $ty:ty, $mantissa_bits:expr, $method_name:ident) => { + mod $mod_name { + use {Rand, Rng, Open01, Closed01}; + + const SCALE: $ty = (1u64 << $mantissa_bits) as $ty; + + impl Rand for $ty { + /// Generate a floating point number in the half-open + /// interval `[0,1)`. + /// + /// See `Closed01` for the closed interval `[0,1]`, + /// and `Open01` for the open interval `(0,1)`. + #[inline] + fn rand<R: Rng>(rng: &mut R) -> $ty { + rng.$method_name() + } + } + impl Rand for Open01<$ty> { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> Open01<$ty> { + // add a small amount (specifically 2 bits below + // the precision of f64/f32 at 1.0), so that small + // numbers are larger than 0, but large numbers + // aren't pushed to/above 1. + Open01(rng.$method_name() + 0.25 / SCALE) + } + } + impl Rand for Closed01<$ty> { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> Closed01<$ty> { + // rescale so that 1.0 - epsilon becomes 1.0 + // precisely. + Closed01(rng.$method_name() * SCALE / (SCALE - 1.0)) + } + } + } + } +} +float_impls! { f64_rand_impls, f64, 53, next_f64 } +float_impls! { f32_rand_impls, f32, 24, next_f32 } + +impl Rand for char { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> char { + // a char is 21 bits + const CHAR_MASK: u32 = 0x001f_ffff; + loop { + // Rejection sampling. About 0.2% of numbers with at most + // 21-bits are invalid codepoints (surrogates), so this + // will succeed first go almost every time. + match char::from_u32(rng.next_u32() & CHAR_MASK) { + Some(c) => return c, + None => {} + } + } + } +} + +impl Rand for bool { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> bool { + rng.gen::<u8>() & 1 == 1 + } +} + +macro_rules! tuple_impl { + // use variables to indicate the arity of the tuple + ($($tyvar:ident),* ) => { + // the trailing commas are for the 1 tuple + impl< + $( $tyvar : Rand ),* + > Rand for ( $( $tyvar ),* , ) { + + #[inline] + fn rand<R: Rng>(_rng: &mut R) -> ( $( $tyvar ),* , ) { + ( + // use the $tyvar's to get the appropriate number of + // repeats (they're not actually needed) + $( + _rng.gen::<$tyvar>() + ),* + , + ) + } + } + } +} + +impl Rand for () { + #[inline] + fn rand<R: Rng>(_: &mut R) -> () { () } +} +tuple_impl!{A} +tuple_impl!{A, B} +tuple_impl!{A, B, C} +tuple_impl!{A, B, C, D} +tuple_impl!{A, B, C, D, E} +tuple_impl!{A, B, C, D, E, F} +tuple_impl!{A, B, C, D, E, F, G} +tuple_impl!{A, B, C, D, E, F, G, H} +tuple_impl!{A, B, C, D, E, F, G, H, I} +tuple_impl!{A, B, C, D, E, F, G, H, I, J} +tuple_impl!{A, B, C, D, E, F, G, H, I, J, K} +tuple_impl!{A, B, C, D, E, F, G, H, I, J, K, L} + +macro_rules! array_impl { + {$n:expr, $t:ident, $($ts:ident,)*} => { + array_impl!{($n - 1), $($ts,)*} + + impl<T> Rand for [T; $n] where T: Rand { + #[inline] + fn rand<R: Rng>(_rng: &mut R) -> [T; $n] { + [_rng.gen::<$t>(), $(_rng.gen::<$ts>()),*] + } + } + }; + {$n:expr,} => { + impl<T> Rand for [T; $n] { + fn rand<R: Rng>(_rng: &mut R) -> [T; $n] { [] } + } + }; +} + +array_impl!{32, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,} + +impl<T:Rand> Rand for Option<T> { + #[inline] + fn rand<R: Rng>(rng: &mut R) -> Option<T> { + if rng.gen() { + Some(rng.gen()) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use {Rng, thread_rng, Open01, Closed01}; + + struct ConstantRng(u64); + impl Rng for ConstantRng { + fn next_u32(&mut self) -> u32 { + let ConstantRng(v) = *self; + v as u32 + } + fn next_u64(&mut self) -> u64 { + let ConstantRng(v) = *self; + v + } + } + + #[test] + fn floating_point_edge_cases() { + // the test for exact equality is correct here. + assert!(ConstantRng(0xffff_ffff).gen::<f32>() != 1.0); + assert!(ConstantRng(0xffff_ffff_ffff_ffff).gen::<f64>() != 1.0); + } + + #[test] + fn rand_open() { + // this is unlikely to catch an incorrect implementation that + // generates exactly 0 or 1, but it keeps it sane. + let mut rng = thread_rng(); + for _ in 0..1_000 { + // strict inequalities + let Open01(f) = rng.gen::<Open01<f64>>(); + assert!(0.0 < f && f < 1.0); + + let Open01(f) = rng.gen::<Open01<f32>>(); + assert!(0.0 < f && f < 1.0); + } + } + + #[test] + fn rand_closed() { + let mut rng = thread_rng(); + for _ in 0..1_000 { + // strict inequalities + let Closed01(f) = rng.gen::<Closed01<f64>>(); + assert!(0.0 <= f && f <= 1.0); + + let Closed01(f) = rng.gen::<Closed01<f32>>(); + assert!(0.0 <= f && f <= 1.0); + } + } +} diff --git a/rand/src/read.rs b/rand/src/read.rs new file mode 100644 index 0000000..c7351b7 --- /dev/null +++ b/rand/src/read.rs @@ -0,0 +1,123 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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 wrapper around any Read to treat it as an RNG. + +use std::io::{self, Read}; +use std::mem; +use Rng; + +/// An RNG that reads random bytes straight from a `Read`. This will +/// work best with an infinite reader, but this is not required. +/// +/// # Panics +/// +/// It will panic if it there is insufficient data to fulfill a request. +/// +/// # Example +/// +/// ```rust +/// use rand::{read, Rng}; +/// +/// let data = vec![1, 2, 3, 4, 5, 6, 7, 8]; +/// let mut rng = read::ReadRng::new(&data[..]); +/// println!("{:x}", rng.gen::<u32>()); +/// ``` +#[derive(Debug)] +pub struct ReadRng<R> { + reader: R +} + +impl<R: Read> ReadRng<R> { + /// Create a new `ReadRng` from a `Read`. + pub fn new(r: R) -> ReadRng<R> { + ReadRng { + reader: r + } + } +} + +impl<R: Read> Rng for ReadRng<R> { + fn next_u32(&mut self) -> u32 { + // This is designed for speed: reading a LE integer on a LE + // platform just involves blitting the bytes into the memory + // of the u32, similarly for BE on BE; avoiding byteswapping. + let mut buf = [0; 4]; + fill(&mut self.reader, &mut buf).unwrap(); + unsafe { *(buf.as_ptr() as *const u32) } + } + fn next_u64(&mut self) -> u64 { + // see above for explanation. + let mut buf = [0; 8]; + fill(&mut self.reader, &mut buf).unwrap(); + unsafe { *(buf.as_ptr() as *const u64) } + } + fn fill_bytes(&mut self, v: &mut [u8]) { + if v.len() == 0 { return } + fill(&mut self.reader, v).unwrap(); + } +} + +fn fill(r: &mut Read, mut buf: &mut [u8]) -> io::Result<()> { + while buf.len() > 0 { + match try!(r.read(buf)) { + 0 => return Err(io::Error::new(io::ErrorKind::Other, + "end of file reached")), + n => buf = &mut mem::replace(&mut buf, &mut [])[n..], + } + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::ReadRng; + use Rng; + + #[test] + fn test_reader_rng_u64() { + // transmute from the target to avoid endianness concerns. + let v = vec![0u8, 0, 0, 0, 0, 0, 0, 1, + 0 , 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 3]; + let mut rng = ReadRng::new(&v[..]); + + assert_eq!(rng.next_u64(), 1_u64.to_be()); + assert_eq!(rng.next_u64(), 2_u64.to_be()); + assert_eq!(rng.next_u64(), 3_u64.to_be()); + } + #[test] + fn test_reader_rng_u32() { + let v = vec![0u8, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]; + let mut rng = ReadRng::new(&v[..]); + + assert_eq!(rng.next_u32(), 1_u32.to_be()); + assert_eq!(rng.next_u32(), 2_u32.to_be()); + assert_eq!(rng.next_u32(), 3_u32.to_be()); + } + #[test] + fn test_reader_rng_fill_bytes() { + let v = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let mut w = [0u8; 8]; + + let mut rng = ReadRng::new(&v[..]); + rng.fill_bytes(&mut w); + + assert!(v == w); + } + + #[test] + #[should_panic] + fn test_reader_rng_insufficient_bytes() { + let mut rng = ReadRng::new(&[][..]); + let mut v = [0u8; 3]; + rng.fill_bytes(&mut v); + } +} diff --git a/rand/src/reseeding.rs b/rand/src/reseeding.rs new file mode 100644 index 0000000..1f24e20 --- /dev/null +++ b/rand/src/reseeding.rs @@ -0,0 +1,229 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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 wrapper around another RNG that reseeds it after it +//! generates a certain number of random bytes. + +use core::default::Default; + +use {Rng, SeedableRng}; + +/// How many bytes of entropy the underling RNG is allowed to generate +/// before it is reseeded +const DEFAULT_GENERATION_THRESHOLD: u64 = 32 * 1024; + +/// A wrapper around any RNG which reseeds the underlying RNG after it +/// has generated a certain number of random bytes. +#[derive(Debug)] +pub struct ReseedingRng<R, Rsdr> { + rng: R, + generation_threshold: u64, + bytes_generated: u64, + /// Controls the behaviour when reseeding the RNG. + pub reseeder: Rsdr, +} + +impl<R: Rng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> { + /// Create a new `ReseedingRng` with the given parameters. + /// + /// # Arguments + /// + /// * `rng`: the random number generator to use. + /// * `generation_threshold`: the number of bytes of entropy at which to reseed the RNG. + /// * `reseeder`: the reseeding object to use. + pub fn new(rng: R, generation_threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> { + ReseedingRng { + rng: rng, + generation_threshold: generation_threshold, + bytes_generated: 0, + reseeder: reseeder + } + } + + /// Reseed the internal RNG if the number of bytes that have been + /// generated exceed the threshold. + pub fn reseed_if_necessary(&mut self) { + if self.bytes_generated >= self.generation_threshold { + self.reseeder.reseed(&mut self.rng); + self.bytes_generated = 0; + } + } +} + + +impl<R: Rng, Rsdr: Reseeder<R>> Rng for ReseedingRng<R, Rsdr> { + fn next_u32(&mut self) -> u32 { + self.reseed_if_necessary(); + self.bytes_generated += 4; + self.rng.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.reseed_if_necessary(); + self.bytes_generated += 8; + self.rng.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.reseed_if_necessary(); + self.bytes_generated += dest.len() as u64; + self.rng.fill_bytes(dest) + } +} + +impl<S, R: SeedableRng<S>, Rsdr: Reseeder<R> + Default> + SeedableRng<(Rsdr, S)> for ReseedingRng<R, Rsdr> { + fn reseed(&mut self, (rsdr, seed): (Rsdr, S)) { + self.rng.reseed(seed); + self.reseeder = rsdr; + self.bytes_generated = 0; + } + + /// Create a new `ReseedingRng` from the given reseeder and + /// seed. This uses a default value for `generation_threshold`. + fn from_seed((rsdr, seed): (Rsdr, S)) -> ReseedingRng<R, Rsdr> { + ReseedingRng { + rng: SeedableRng::from_seed(seed), + generation_threshold: DEFAULT_GENERATION_THRESHOLD, + bytes_generated: 0, + reseeder: rsdr + } + } +} + +/// Something that can be used to reseed an RNG via `ReseedingRng`. +/// +/// # Example +/// +/// ```rust +/// use rand::{Rng, SeedableRng, StdRng}; +/// use rand::reseeding::{Reseeder, ReseedingRng}; +/// +/// struct TickTockReseeder { tick: bool } +/// impl Reseeder<StdRng> for TickTockReseeder { +/// fn reseed(&mut self, rng: &mut StdRng) { +/// let val = if self.tick {0} else {1}; +/// rng.reseed(&[val]); +/// self.tick = !self.tick; +/// } +/// } +/// fn main() { +/// let rsdr = TickTockReseeder { tick: true }; +/// +/// let inner = StdRng::new().unwrap(); +/// let mut rng = ReseedingRng::new(inner, 10, rsdr); +/// +/// // this will repeat, because it gets reseeded very regularly. +/// let s: String = rng.gen_ascii_chars().take(100).collect(); +/// println!("{}", s); +/// } +/// +/// ``` +pub trait Reseeder<R> { + /// Reseed the given RNG. + fn reseed(&mut self, rng: &mut R); +} + +/// Reseed an RNG using a `Default` instance. This reseeds by +/// replacing the RNG with the result of a `Default::default` call. +#[derive(Clone, Copy, Debug)] +pub struct ReseedWithDefault; + +impl<R: Rng + Default> Reseeder<R> for ReseedWithDefault { + fn reseed(&mut self, rng: &mut R) { + *rng = Default::default(); + } +} +impl Default for ReseedWithDefault { + fn default() -> ReseedWithDefault { ReseedWithDefault } +} + +#[cfg(test)] +mod test { + use std::default::Default; + use std::iter::repeat; + use super::{ReseedingRng, ReseedWithDefault}; + use {SeedableRng, Rng}; + + struct Counter { + i: u32 + } + + impl Rng for Counter { + fn next_u32(&mut self) -> u32 { + self.i += 1; + // very random + self.i - 1 + } + } + impl Default for Counter { + fn default() -> Counter { + Counter { i: 0 } + } + } + impl SeedableRng<u32> for Counter { + fn reseed(&mut self, seed: u32) { + self.i = seed; + } + fn from_seed(seed: u32) -> Counter { + Counter { i: seed } + } + } + type MyRng = ReseedingRng<Counter, ReseedWithDefault>; + + #[test] + fn test_reseeding() { + let mut rs = ReseedingRng::new(Counter {i:0}, 400, ReseedWithDefault); + + let mut i = 0; + for _ in 0..1000 { + assert_eq!(rs.next_u32(), i % 100); + i += 1; + } + } + + #[test] + fn test_rng_seeded() { + let mut ra: MyRng = SeedableRng::from_seed((ReseedWithDefault, 2)); + let mut rb: MyRng = SeedableRng::from_seed((ReseedWithDefault, 2)); + assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), + rb.gen_ascii_chars().take(100))); + } + + #[test] + fn test_rng_reseed() { + let mut r: MyRng = SeedableRng::from_seed((ReseedWithDefault, 3)); + let string1: String = r.gen_ascii_chars().take(100).collect(); + + r.reseed((ReseedWithDefault, 3)); + + let string2: String = r.gen_ascii_chars().take(100).collect(); + assert_eq!(string1, string2); + } + + const FILL_BYTES_V_LEN: usize = 13579; + #[test] + fn test_rng_fill_bytes() { + let mut v = repeat(0u8).take(FILL_BYTES_V_LEN).collect::<Vec<_>>(); + ::test::rng().fill_bytes(&mut v); + + // Sanity test: if we've gotten here, `fill_bytes` has not infinitely + // recursed. + assert_eq!(v.len(), FILL_BYTES_V_LEN); + + // To test that `fill_bytes` actually did something, check that the + // average of `v` is not 0. + let mut sum = 0.0; + for &x in v.iter() { + sum += x as f64; + } + assert!(sum / v.len() as f64 != 0.0); + } +} diff --git a/rand/src/seq.rs b/rand/src/seq.rs new file mode 100644 index 0000000..a7889fe --- /dev/null +++ b/rand/src/seq.rs @@ -0,0 +1,337 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +//! Functions for randomly accessing and sampling sequences. + +use super::Rng; + +// This crate is only enabled when either std or alloc is available. +// BTreeMap is not as fast in tests, but better than nothing. +#[cfg(feature="std")] use std::collections::HashMap; +#[cfg(not(feature="std"))] use alloc::btree_map::BTreeMap; + +#[cfg(not(feature="std"))] use alloc::Vec; + +/// Randomly sample `amount` elements from a finite iterator. +/// +/// The following can be returned: +/// - `Ok`: `Vec` of `amount` non-repeating randomly sampled elements. The order is not random. +/// - `Err`: `Vec` of all the elements from `iterable` in sequential order. This happens when the +/// length of `iterable` was less than `amount`. This is considered an error since exactly +/// `amount` elements is typically expected. +/// +/// This implementation uses `O(len(iterable))` time and `O(amount)` memory. +/// +/// # Example +/// +/// ```rust +/// use rand::{thread_rng, seq}; +/// +/// let mut rng = thread_rng(); +/// let sample = seq::sample_iter(&mut rng, 1..100, 5).unwrap(); +/// println!("{:?}", sample); +/// ``` +pub fn sample_iter<T, I, R>(rng: &mut R, iterable: I, amount: usize) -> Result<Vec<T>, Vec<T>> + where I: IntoIterator<Item=T>, + R: Rng, +{ + let mut iter = iterable.into_iter(); + let mut reservoir = Vec::with_capacity(amount); + reservoir.extend(iter.by_ref().take(amount)); + + // Continue unless the iterator was exhausted + // + // note: this prevents iterators that "restart" from causing problems. + // If the iterator stops once, then so do we. + if reservoir.len() == amount { + for (i, elem) in iter.enumerate() { + let k = rng.gen_range(0, i + 1 + amount); + if let Some(spot) = reservoir.get_mut(k) { + *spot = elem; + } + } + Ok(reservoir) + } else { + // Don't hang onto extra memory. There is a corner case where + // `amount` was much less than `len(iterable)`. + reservoir.shrink_to_fit(); + Err(reservoir) + } +} + +/// Randomly sample exactly `amount` values from `slice`. +/// +/// The values are non-repeating and in random order. +/// +/// This implementation uses `O(amount)` time and memory. +/// +/// Panics if `amount > slice.len()` +/// +/// # Example +/// +/// ```rust +/// use rand::{thread_rng, seq}; +/// +/// let mut rng = thread_rng(); +/// let values = vec![5, 6, 1, 3, 4, 6, 7]; +/// println!("{:?}", seq::sample_slice(&mut rng, &values, 3)); +/// ``` +pub fn sample_slice<R, T>(rng: &mut R, slice: &[T], amount: usize) -> Vec<T> + where R: Rng, + T: Clone +{ + let indices = sample_indices(rng, slice.len(), amount); + + let mut out = Vec::with_capacity(amount); + out.extend(indices.iter().map(|i| slice[*i].clone())); + out +} + +/// Randomly sample exactly `amount` references from `slice`. +/// +/// The references are non-repeating and in random order. +/// +/// This implementation uses `O(amount)` time and memory. +/// +/// Panics if `amount > slice.len()` +/// +/// # Example +/// +/// ```rust +/// use rand::{thread_rng, seq}; +/// +/// let mut rng = thread_rng(); +/// let values = vec![5, 6, 1, 3, 4, 6, 7]; +/// println!("{:?}", seq::sample_slice_ref(&mut rng, &values, 3)); +/// ``` +pub fn sample_slice_ref<'a, R, T>(rng: &mut R, slice: &'a [T], amount: usize) -> Vec<&'a T> + where R: Rng +{ + let indices = sample_indices(rng, slice.len(), amount); + + let mut out = Vec::with_capacity(amount); + out.extend(indices.iter().map(|i| &slice[*i])); + out +} + +/// Randomly sample exactly `amount` indices from `0..length`. +/// +/// The values are non-repeating and in random order. +/// +/// This implementation uses `O(amount)` time and memory. +/// +/// This method is used internally by the slice sampling methods, but it can sometimes be useful to +/// have the indices themselves so this is provided as an alternative. +/// +/// Panics if `amount > length` +pub fn sample_indices<R>(rng: &mut R, length: usize, amount: usize) -> Vec<usize> + where R: Rng, +{ + if amount > length { + panic!("`amount` must be less than or equal to `slice.len()`"); + } + + // We are going to have to allocate at least `amount` for the output no matter what. However, + // if we use the `cached` version we will have to allocate `amount` as a HashMap as well since + // it inserts an element for every loop. + // + // Therefore, if `amount >= length / 2` then inplace will be both faster and use less memory. + // In fact, benchmarks show the inplace version is faster for length up to about 20 times + // faster than amount. + // + // TODO: there is probably even more fine-tuning that can be done here since + // `HashMap::with_capacity(amount)` probably allocates more than `amount` in practice, + // and a trade off could probably be made between memory/cpu, since hashmap operations + // are slower than array index swapping. + if amount >= length / 20 { + sample_indices_inplace(rng, length, amount) + } else { + sample_indices_cache(rng, length, amount) + } +} + +/// Sample an amount of indices using an inplace partial fisher yates method. +/// +/// This allocates the entire `length` of indices and randomizes only the first `amount`. +/// It then truncates to `amount` and returns. +/// +/// This is better than using a HashMap "cache" when `amount >= length / 2` since it does not +/// require allocating an extra cache and is much faster. +fn sample_indices_inplace<R>(rng: &mut R, length: usize, amount: usize) -> Vec<usize> + where R: Rng, +{ + debug_assert!(amount <= length); + let mut indices: Vec<usize> = Vec::with_capacity(length); + indices.extend(0..length); + for i in 0..amount { + let j: usize = rng.gen_range(i, length); + let tmp = indices[i]; + indices[i] = indices[j]; + indices[j] = tmp; + } + indices.truncate(amount); + debug_assert_eq!(indices.len(), amount); + indices +} + + +/// This method performs a partial fisher-yates on a range of indices using a HashMap +/// as a cache to record potential collisions. +/// +/// The cache avoids allocating the entire `length` of values. This is especially useful when +/// `amount <<< length`, i.e. select 3 non-repeating from 1_000_000 +fn sample_indices_cache<R>( + rng: &mut R, + length: usize, + amount: usize, +) -> Vec<usize> + where R: Rng, +{ + debug_assert!(amount <= length); + #[cfg(feature="std")] let mut cache = HashMap::with_capacity(amount); + #[cfg(not(feature="std"))] let mut cache = BTreeMap::new(); + let mut out = Vec::with_capacity(amount); + for i in 0..amount { + let j: usize = rng.gen_range(i, length); + + // equiv: let tmp = slice[i]; + let tmp = match cache.get(&i) { + Some(e) => *e, + None => i, + }; + + // equiv: slice[i] = slice[j]; + let x = match cache.get(&j) { + Some(x) => *x, + None => j, + }; + + // equiv: slice[j] = tmp; + cache.insert(j, tmp); + + // note that in the inplace version, slice[i] is automatically "returned" value + out.push(x); + } + debug_assert_eq!(out.len(), amount); + out +} + +#[cfg(test)] +mod test { + use super::*; + use {thread_rng, XorShiftRng, SeedableRng}; + + #[test] + fn test_sample_iter() { + let min_val = 1; + let max_val = 100; + + let mut r = thread_rng(); + let vals = (min_val..max_val).collect::<Vec<i32>>(); + let small_sample = sample_iter(&mut r, vals.iter(), 5).unwrap(); + let large_sample = sample_iter(&mut r, vals.iter(), vals.len() + 5).unwrap_err(); + + assert_eq!(small_sample.len(), 5); + assert_eq!(large_sample.len(), vals.len()); + // no randomization happens when amount >= len + assert_eq!(large_sample, vals.iter().collect::<Vec<_>>()); + + assert!(small_sample.iter().all(|e| { + **e >= min_val && **e <= max_val + })); + } + #[test] + fn test_sample_slice_boundaries() { + let empty: &[u8] = &[]; + + let mut r = thread_rng(); + + // sample 0 items + assert_eq!(sample_slice(&mut r, empty, 0), vec![]); + assert_eq!(sample_slice(&mut r, &[42, 2, 42], 0), vec![]); + + // sample 1 item + assert_eq!(sample_slice(&mut r, &[42], 1), vec![42]); + let v = sample_slice(&mut r, &[1, 42], 1)[0]; + assert!(v == 1 || v == 42); + + // sample "all" the items + let v = sample_slice(&mut r, &[42, 133], 2); + assert!(v == vec![42, 133] || v == vec![133, 42]); + + assert_eq!(sample_indices_inplace(&mut r, 0, 0), vec![]); + assert_eq!(sample_indices_inplace(&mut r, 1, 0), vec![]); + assert_eq!(sample_indices_inplace(&mut r, 1, 1), vec![0]); + + assert_eq!(sample_indices_cache(&mut r, 0, 0), vec![]); + assert_eq!(sample_indices_cache(&mut r, 1, 0), vec![]); + assert_eq!(sample_indices_cache(&mut r, 1, 1), vec![0]); + + // Make sure lucky 777's aren't lucky + let slice = &[42, 777]; + let mut num_42 = 0; + let total = 1000; + for _ in 0..total { + let v = sample_slice(&mut r, slice, 1); + assert_eq!(v.len(), 1); + let v = v[0]; + assert!(v == 42 || v == 777); + if v == 42 { + num_42 += 1; + } + } + let ratio_42 = num_42 as f64 / 1000 as f64; + assert!(0.4 <= ratio_42 || ratio_42 <= 0.6, "{}", ratio_42); + } + + #[test] + fn test_sample_slice() { + let xor_rng = XorShiftRng::from_seed; + + let max_range = 100; + let mut r = thread_rng(); + + for length in 1usize..max_range { + let amount = r.gen_range(0, length); + let seed: [u32; 4] = [ + r.next_u32(), r.next_u32(), r.next_u32(), r.next_u32() + ]; + + println!("Selecting indices: len={}, amount={}, seed={:?}", length, amount, seed); + + // assert that the two index methods give exactly the same result + let inplace = sample_indices_inplace( + &mut xor_rng(seed), length, amount); + let cache = sample_indices_cache( + &mut xor_rng(seed), length, amount); + assert_eq!(inplace, cache); + + // assert the basics work + let regular = sample_indices( + &mut xor_rng(seed), length, amount); + assert_eq!(regular.len(), amount); + assert!(regular.iter().all(|e| *e < length)); + assert_eq!(regular, inplace); + + // also test that sampling the slice works + let vec: Vec<usize> = (0..length).collect(); + { + let result = sample_slice(&mut xor_rng(seed), &vec, amount); + assert_eq!(result, regular); + } + + { + let result = sample_slice_ref(&mut xor_rng(seed), &vec, amount); + let expected = regular.iter().map(|v| v).collect::<Vec<_>>(); + assert_eq!(result, expected); + } + } + } +} diff --git a/rand/utils/ziggurat_tables.py b/rand/utils/ziggurat_tables.py new file mode 100755 index 0000000..762f956 --- /dev/null +++ b/rand/utils/ziggurat_tables.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Rust Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution and at +# http://rust-lang.org/COPYRIGHT. +# +# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +# http://www.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. + +# This creates the tables used for distributions implemented using the +# ziggurat algorithm in `rand::distributions;`. They are +# (basically) the tables as used in the ZIGNOR variant (Doornik 2005). +# They are changed rarely, so the generated file should be checked in +# to git. +# +# It creates 3 tables: X as in the paper, F which is f(x_i), and +# F_DIFF which is f(x_i) - f(x_{i-1}). The latter two are just cached +# values which is not done in that paper (but is done in other +# variants). Note that the adZigR table is unnecessary because of +# algebra. +# +# It is designed to be compatible with Python 2 and 3. + +from math import exp, sqrt, log, floor +import random + +# The order should match the return value of `tables` +TABLE_NAMES = ['X', 'F'] + +# The actual length of the table is 1 more, to stop +# index-out-of-bounds errors. This should match the bitwise operation +# to find `i` in `zigurrat` in `libstd/rand/mod.rs`. Also the *_R and +# *_V constants below depend on this value. +TABLE_LEN = 256 + +# equivalent to `zigNorInit` in Doornik2005, but generalised to any +# distribution. r = dR, v = dV, f = probability density function, +# f_inv = inverse of f +def tables(r, v, f, f_inv): + # compute the x_i + xvec = [0]*(TABLE_LEN+1) + + xvec[0] = v / f(r) + xvec[1] = r + + for i in range(2, TABLE_LEN): + last = xvec[i-1] + xvec[i] = f_inv(v / last + f(last)) + + # cache the f's + fvec = [0]*(TABLE_LEN+1) + for i in range(TABLE_LEN+1): + fvec[i] = f(xvec[i]) + + return xvec, fvec + +# Distributions +# N(0, 1) +def norm_f(x): + return exp(-x*x/2.0) +def norm_f_inv(y): + return sqrt(-2.0*log(y)) + +NORM_R = 3.6541528853610088 +NORM_V = 0.00492867323399 + +NORM = tables(NORM_R, NORM_V, + norm_f, norm_f_inv) + +# Exp(1) +def exp_f(x): + return exp(-x) +def exp_f_inv(y): + return -log(y) + +EXP_R = 7.69711747013104972 +EXP_V = 0.0039496598225815571993 + +EXP = tables(EXP_R, EXP_V, + exp_f, exp_f_inv) + + +# Output the tables/constants/types + +def render_static(name, type, value): + # no space or + return 'pub static %s: %s =%s;\n' % (name, type, value) + +# static `name`: [`type`, .. `len(values)`] = +# [values[0], ..., values[3], +# values[4], ..., values[7], +# ... ]; +def render_table(name, values): + rows = [] + # 4 values on each row + for i in range(0, len(values), 4): + row = values[i:i+4] + rows.append(', '.join('%.18f' % f for f in row)) + + rendered = '\n [%s]' % ',\n '.join(rows) + return render_static(name, '[f64, .. %d]' % len(values), rendered) + + +with open('ziggurat_tables.rs', 'w') as f: + f.write('''// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.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. + +// Tables for distributions which are sampled using the ziggurat +// algorithm. Autogenerated by `ziggurat_tables.py`. + +pub type ZigTable = &\'static [f64, .. %d]; +''' % (TABLE_LEN + 1)) + for name, tables, r in [('NORM', NORM, NORM_R), + ('EXP', EXP, EXP_R)]: + f.write(render_static('ZIG_%s_R' % name, 'f64', ' %.18f' % r)) + for (tabname, table) in zip(TABLE_NAMES, tables): + f.write(render_table('ZIG_%s_%s' % (name, tabname), table)) |