From c44b0c35baf1cdb10bd8e39986d02323294fab15 Mon Sep 17 00:00:00 2001 From: shimunn Date: Wed, 25 Sep 2019 17:31:44 +0200 Subject: [PATCH] vendor patches & crates.io compliant metadata --- Cargo.lock | 20 +- Cargo.toml | 24 +- patch/cryptsetup-rs/.gitignore | 4 + patch/cryptsetup-rs/.travis.yml | 22 + patch/cryptsetup-rs/Cargo.toml | 32 + patch/cryptsetup-rs/LICENSE | 165 ++++ patch/cryptsetup-rs/README.md | 29 + patch/cryptsetup-rs/blkid-rs/.gitignore | 2 + patch/cryptsetup-rs/blkid-rs/Cargo.toml | 18 + patch/cryptsetup-rs/blkid-rs/src/lib.rs | 286 +++++++ patch/cryptsetup-rs/examples/luks_dump.rs | 78 ++ .../libcryptsetup-sys/Cargo.toml | 21 + .../cryptsetup-rs/libcryptsetup-sys/build.rs | 8 + patch/cryptsetup-rs/libcryptsetup-sys/lib.rs | 492 ++++++++++++ patch/cryptsetup-rs/rustfmt.toml | 2 + patch/cryptsetup-rs/src/api.rs | 377 +++++++++ patch/cryptsetup-rs/src/device.rs | 319 ++++++++ patch/cryptsetup-rs/src/lib.rs | 32 + patch/cryptsetup-rs/tests/tests.rs | 72 ++ patch/ctap/.gitignore | 4 + patch/ctap/Cargo.toml | 21 + patch/ctap/README.md | 59 ++ patch/ctap/src/LICENSE-APACHE | 201 +++++ patch/ctap/src/LICENSE-MIT | 19 + patch/ctap/src/cbor.rs | 715 ++++++++++++++++++ patch/ctap/src/crypto.rs | 130 ++++ patch/ctap/src/error.rs | 101 +++ patch/ctap/src/extensions/hmac.rs | 200 +++++ patch/ctap/src/extensions/mod.rs | 1 + patch/ctap/src/hid_common.rs | 16 + patch/ctap/src/hid_linux.rs | 88 +++ patch/ctap/src/lib.rs | 402 ++++++++++ patch/ctap/src/packet.rs | 179 +++++ 33 files changed, 4120 insertions(+), 19 deletions(-) create mode 100644 patch/cryptsetup-rs/.gitignore create mode 100644 patch/cryptsetup-rs/.travis.yml create mode 100644 patch/cryptsetup-rs/Cargo.toml create mode 100644 patch/cryptsetup-rs/LICENSE create mode 100644 patch/cryptsetup-rs/README.md create mode 100644 patch/cryptsetup-rs/blkid-rs/.gitignore create mode 100644 patch/cryptsetup-rs/blkid-rs/Cargo.toml create mode 100644 patch/cryptsetup-rs/blkid-rs/src/lib.rs create mode 100644 patch/cryptsetup-rs/examples/luks_dump.rs create mode 100644 patch/cryptsetup-rs/libcryptsetup-sys/Cargo.toml create mode 100644 patch/cryptsetup-rs/libcryptsetup-sys/build.rs create mode 100644 patch/cryptsetup-rs/libcryptsetup-sys/lib.rs create mode 100644 patch/cryptsetup-rs/rustfmt.toml create mode 100644 patch/cryptsetup-rs/src/api.rs create mode 100644 patch/cryptsetup-rs/src/device.rs create mode 100644 patch/cryptsetup-rs/src/lib.rs create mode 100644 patch/cryptsetup-rs/tests/tests.rs create mode 100644 patch/ctap/.gitignore create mode 100644 patch/ctap/Cargo.toml create mode 100644 patch/ctap/README.md create mode 100644 patch/ctap/src/LICENSE-APACHE create mode 100644 patch/ctap/src/LICENSE-MIT create mode 100644 patch/ctap/src/cbor.rs create mode 100644 patch/ctap/src/crypto.rs create mode 100644 patch/ctap/src/error.rs create mode 100644 patch/ctap/src/extensions/hmac.rs create mode 100644 patch/ctap/src/extensions/mod.rs create mode 100644 patch/ctap/src/hid_common.rs create mode 100644 patch/ctap/src/hid_linux.rs create mode 100644 patch/ctap/src/lib.rs create mode 100644 patch/ctap/src/packet.rs diff --git a/Cargo.lock b/Cargo.lock index 074fbc3..e0b74bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "blkid-rs" version = "0.1.1" -source = "git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot#09189246eac5b930d13a4e0d78d75157bf07c832" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -105,12 +104,11 @@ dependencies = [ [[package]] name = "cryptsetup-rs" version = "0.2.0" -source = "git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot#09189246eac5b930d13a4e0d78d75157bf07c832" dependencies = [ - "blkid-rs 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)", + "blkid-rs 0.1.1", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "libcryptsetup-sys 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)", + "libcryptsetup-sys 0.1.1", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -118,7 +116,6 @@ dependencies = [ [[package]] name = "ctap" version = "0.1.0" -source = "git+https://github.com/shimunn/ctap.git?branch=hmac_ext#3d3679d5b9a8c8cc90edb969c0f187740a3f2480" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "cbor-codec 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -173,13 +170,13 @@ dependencies = [ [[package]] name = "fido2luks" -version = "0.1.0" +version = "0.2.1" dependencies = [ - "cryptsetup-rs 0.2.0 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)", - "ctap 0.1.0 (git+https://github.com/shimunn/ctap.git?branch=hmac_ext)", + "cryptsetup-rs 0.2.0", + "ctap 0.1.0", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libcryptsetup-sys 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)", + "libcryptsetup-sys 0.1.1", "rpassword 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -221,7 +218,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libcryptsetup-sys" version = "0.1.1" -source = "git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot#09189246eac5b930d13a4e0d78d75157bf07c832" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -612,15 +608,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "5180c5a20655b14a819b652fd2378fa5f1697b6c9ddad3e695c2f9cedf6df4e2" "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" -"checksum blkid-rs 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)" = "" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum cbor-codec 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e083a023562b37c52837e850131a51b1154cceb9d149f41ee3d386737b140f46" "checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum cryptsetup-rs 0.2.0 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)" = "" -"checksum ctap 0.1.0 (git+https://github.com/shimunn/ctap.git?branch=hmac_ext)" = "" "checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" "checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" @@ -631,7 +624,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" -"checksum libcryptsetup-sys 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)" = "" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" diff --git a/Cargo.toml b/Cargo.toml index b26501c..dfe1d16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "fido2luks" -version = "0.2.0" +version = "0.2.1" authors = ["shimunn "] edition = "2018" +description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator" +license = "GPL v3" +repository = "https://github.com/shimunn/fido2luks" [dependencies] -ctap = { git = "https://github.com/shimunn/ctap.git", branch = "hmac_ext" } -#cryptsetup-rs = "0.2.0" -cryptsetup-rs = { git = "https://github.com/shimunn/cryptsetup-rs.git", branch = "update_keyslot" } -libcryptsetup-sys = { git = "https://github.com/shimunn/cryptsetup-rs.git", branch = "update_keyslot" } +ctap = "0.1.0" +cryptsetup-rs = "0.2.0" +libcryptsetup-sys = "0.1.1" hex = "0.3.2" rust-crypto = "0.2.36" @@ -16,9 +18,21 @@ failure = "0.1.5" rpassword = "4.0.1" structopt = "0.3.2" +[patch.crates-io] +#Until https://github.com/solidninja/cryptsetup-rs/pull/2 merges +cryptsetup-rs = { path = "./patch/cryptsetup-rs" } +libcryptsetup-sys = { path = "./patch/cryptsetup-rs/libcryptsetup-sys" } +ctap = { path = "./patch/ctap" } + [profile.release] lto = true opt-level = 'z' panic = 'abort' incremental = false overflow-checks = false + +[package.metadata.rpm] +buildflags = ["--release"] + +[package.metadata.rpm.targets] +fido2luks = { path = "/usr/bin/fido2luks" } diff --git a/patch/cryptsetup-rs/.gitignore b/patch/cryptsetup-rs/.gitignore new file mode 100644 index 0000000..d3c508e --- /dev/null +++ b/patch/cryptsetup-rs/.gitignore @@ -0,0 +1,4 @@ +target/ +Cargo.lock +*.iml +.idea/ \ No newline at end of file diff --git a/patch/cryptsetup-rs/.travis.yml b/patch/cryptsetup-rs/.travis.yml new file mode 100644 index 0000000..f009a9c --- /dev/null +++ b/patch/cryptsetup-rs/.travis.yml @@ -0,0 +1,22 @@ +language: rust +rust: + - stable + - beta + - nightly +matrix: + allow_failures: + - rust: nightly +sudo: required +dist: trusty +os: + - linux +script: + - cargo build --all-targets --verbose + - sudo /bin/sh -c "PATH=/home/travis/.cargo/bin:$PATH LD_LIBRARY_PATH=/home/travis/.cargo/lib cargo test --verbose" +addons: + apt: + packages: + - libcryptsetup-dev +notifications: + email: + on_success: never \ No newline at end of file diff --git a/patch/cryptsetup-rs/Cargo.toml b/patch/cryptsetup-rs/Cargo.toml new file mode 100644 index 0000000..6478697 --- /dev/null +++ b/patch/cryptsetup-rs/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Vladimir Lushnikov "] +description = "Rust wrapper around the libcryptsetup library, allowing manipulation of LUKS devices in Linux" +homepage = "https://github.com/solidninja/cryptsetup-rs" +license = "LGPL-3.0" +name = "cryptsetup-rs" +version = "0.2.0" + +[dependencies] +errno = "0.2.3" +libc = "0.2.42" +log = "0.4.2" + +[dependencies.blkid-rs] +path = "blkid-rs" +version = "0.1.1" + +[dependencies.libcryptsetup-sys] +path = "libcryptsetup-sys" +version = "0.1.1" + +[dependencies.uuid] +features = ["v4"] +version = "0.6.5" + +[dev-dependencies] +env_logger = "0.5.10" +expectest = "0.10.0" +tempdir = "0.3.7" + +[lib] +name = "cryptsetup_rs" diff --git a/patch/cryptsetup-rs/LICENSE b/patch/cryptsetup-rs/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/patch/cryptsetup-rs/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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/patch/cryptsetup-rs/README.md b/patch/cryptsetup-rs/README.md new file mode 100644 index 0000000..ed4bd8e --- /dev/null +++ b/patch/cryptsetup-rs/README.md @@ -0,0 +1,29 @@ +[![Build Status](https://travis-ci.org/solidninja/cryptsetup-rs.svg?branch=master)](https://travis-ci.org/solidninja/cryptsetup-rs) +[![crates.io Status](https://img.shields.io/crates/v/cryptsetup-rs.svg)](https://crates.io/crates/cryptsetup-rs) +[![docs.rs build](https://docs.rs/cryptsetup-rs/badge.svg)](https://docs.rs/crate/cryptsetup-rs/) + +# cryptsetup-rs - Rust bindings to `libcryptsetup` on Linux + +A safe binding to `libcryptsetup` that allows working with encrypted disks on Linux. + +Features: + * High-level API for open/format/other operations + + +Documentation for the bindings can be found on [docs.rs](https://docs.rs/crate/cryptsetup-rs/). + +The example [`luks_dump.rs`](examples/luks_dump.rs) shows how a command like `cryptsetup luksDump` can +be implemented. + +## TODO + +* Secure string for passing keys +* High-level API for non-LUKS1 disks (truecrypt, verity) +* LUKS2 and cryptsetup2 support + +## Contributing + +`cryptsetup-rs` is the work of its contributors and is a free software project licensed under the +LGPLv3 or later. + +If you would like to contribute, please follow the [C4](https://rfc.zeromq.org/spec:42/C4/) process. diff --git a/patch/cryptsetup-rs/blkid-rs/.gitignore b/patch/cryptsetup-rs/blkid-rs/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/patch/cryptsetup-rs/blkid-rs/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/patch/cryptsetup-rs/blkid-rs/Cargo.toml b/patch/cryptsetup-rs/blkid-rs/Cargo.toml new file mode 100644 index 0000000..bbf2e4a --- /dev/null +++ b/patch/cryptsetup-rs/blkid-rs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "blkid-rs" +version = "0.1.1" +authors = ["Vladimir Lushnikov "] +description = "Rust implementation of blkid for LUKS volumes" +license = "LGPL-3.0" +homepage = "https://github.com/solidninja/libcryptset-rs" + +[lib] +name = "blkid_rs" + +[dependencies] +byteorder = "1.1" +uuid = "0.6" + +[dev-dependencies] +env_logger = "0.4" +tempdir = "0.3" diff --git a/patch/cryptsetup-rs/blkid-rs/src/lib.rs b/patch/cryptsetup-rs/blkid-rs/src/lib.rs new file mode 100644 index 0000000..305cd8b --- /dev/null +++ b/patch/cryptsetup-rs/blkid-rs/src/lib.rs @@ -0,0 +1,286 @@ +// The following code has been ported from libcryptsetup + +extern crate byteorder; +extern crate uuid; + +use std::convert; +use std::io; +use std::io::Read; +use std::mem; +use std::str; + +pub trait LuksHeader { + fn version(&self) -> u16; + fn cipher_name(&self) -> Result<&str, Error>; + fn cipher_mode(&self) -> Result<&str, Error>; + fn hash_spec(&self) -> Result<&str, Error>; + fn payload_offset(&self) -> u32; + fn key_bytes(&self) -> u32; + fn mk_digest(&self) -> &[u8]; + fn mk_digest_salt(&self) -> &[u8]; + fn mk_digest_iterations(&self) -> u32; + fn uuid(&self) -> Result; +} + +#[derive(Debug)] +pub enum Error { + InvalidMagic, + InvalidStringEncoding(str::Utf8Error), + InvalidVersion, + InvalidUuid(uuid::ParseError), + ReadError(io::Error), + ReadIncorrectHeaderSize, + HeaderProcessingError, +} + +pub struct BlockDevice; + +impl BlockDevice { + pub fn read_luks_header(reader: R) -> Result { + let luks_phdr_size = mem::size_of::(); + let mut buf = Vec::::with_capacity(luks_phdr_size); + let read_size = try!(reader.take(luks_phdr_size as u64).read_to_end(&mut buf)); + if read_size != luks_phdr_size { + Err(Error::ReadIncorrectHeaderSize) + } else { + raw::luks_phdr::from_buf(&buf) + } + } +} + +impl convert::From for Error { + fn from(error: str::Utf8Error) -> Error { + Error::InvalidStringEncoding(error) + } +} + +impl convert::From for Error { + fn from(error: uuid::ParseError) -> Error { + Error::InvalidUuid(error) + } +} + +impl convert::From for Error { + fn from(error: io::Error) -> Error { + Error::ReadError(error) + } +} +/* FIXME +impl convert::From for Error { + fn from(error: byteorder::Error) -> Error { + match error { + byteorder::Error::UnexpectedEOF => Error::HeaderProcessingError, + byteorder::Error::Io(io_error) => Error::ReadError(io_error), + } + } +} +*/ + +pub mod raw { + #![allow(non_snake_case)] + + use std::convert::From; + use std::io::{Cursor, Read}; + use std::mem; + use std::str; + + use byteorder::{BigEndian, ReadBytesExt}; + use uuid; + + const LUKS_VERSION_SUPPORTED: u16 = 1; + + const LUKS_MAGIC_L: usize = 6; + const LUKS_CIPHERNAME_L: usize = 32; + const LUKS_CIPHERMODE_L: usize = 32; + const LUKS_HASHSPEC_L: usize = 32; + const LUKS_DIGESTSIZE: usize = 20; + const LUKS_SALTSIZE: usize = 32; + const UUID_STRING_L: usize = 40; + + const LUKS_MAGIC: &'static [u8; LUKS_MAGIC_L] = b"LUKS\xba\xbe"; + + #[repr(C, packed)] + pub struct luks_phdr { + pub magic: [u8; LUKS_MAGIC_L], + pub version: u16, + pub cipherName: [u8; LUKS_CIPHERNAME_L], + pub cipherMode: [u8; LUKS_CIPHERMODE_L], + pub hashSpec: [u8; LUKS_HASHSPEC_L], + pub payloadOffset: u32, + pub keyBytes: u32, + pub mkDigest: [u8; LUKS_DIGESTSIZE], + pub mkDigestSalt: [u8; LUKS_SALTSIZE], + pub mkDigestIterations: u32, + pub uuid: [u8; UUID_STRING_L], + } + + impl luks_phdr { + pub fn from_buf(buf: &[u8]) -> Result { + // FIXME - this is not particularly pretty + + if buf.len() != mem::size_of::() { + return Err(super::Error::ReadIncorrectHeaderSize); + } + let mut cursor = Cursor::new(buf); + let mut magic_buf = [0u8; LUKS_MAGIC_L]; + let magic_len = try!(cursor.read(&mut magic_buf)); + + if magic_len != LUKS_MAGIC_L || magic_buf != &LUKS_MAGIC[..] { + return Err(super::Error::InvalidMagic); + } + + let version = try!(cursor.read_u16::()); + if version != LUKS_VERSION_SUPPORTED { + return Err(super::Error::InvalidVersion); + } + + let mut cipher_name_buf = [0u8; LUKS_CIPHERNAME_L]; + let cipher_name_len = try!(cursor.read(&mut cipher_name_buf)); + if cipher_name_len != LUKS_CIPHERNAME_L { + return Err(super::Error::HeaderProcessingError); + } + + let mut cipher_mode_buf = [0u8; LUKS_CIPHERMODE_L]; + let cipher_mode_len = try!(cursor.read(&mut cipher_mode_buf)); + if cipher_mode_len != LUKS_CIPHERMODE_L { + return Err(super::Error::HeaderProcessingError); + } + + let mut hash_spec_buf = [0u8; LUKS_HASHSPEC_L]; + let hash_spec_len = try!(cursor.read(&mut hash_spec_buf)); + if hash_spec_len != LUKS_HASHSPEC_L { + return Err(super::Error::HeaderProcessingError); + } + + let payload_offset = try!(cursor.read_u32::()); + let key_bytes = try!(cursor.read_u32::()); + + let mut mk_digest_buf = [0u8; LUKS_DIGESTSIZE]; + let mk_digest_len = try!(cursor.read(&mut mk_digest_buf)); + if mk_digest_len != LUKS_DIGESTSIZE { + return Err(super::Error::HeaderProcessingError); + } + + let mut mk_digest_salt_buf = [0u8; LUKS_SALTSIZE]; + let mk_digest_salt_len = try!(cursor.read(&mut mk_digest_salt_buf)); + if mk_digest_salt_len != LUKS_SALTSIZE { + return Err(super::Error::HeaderProcessingError); + } + + let mk_digest_iterations = try!(cursor.read_u32::()); + + let mut uuid_buf = [0u8; UUID_STRING_L]; + let uuid_len = try!(cursor.read(&mut uuid_buf)); + if uuid_len != UUID_STRING_L { + return Err(super::Error::HeaderProcessingError); + } + + let res = luks_phdr { + magic: magic_buf, + version: version, + cipherName: cipher_name_buf, + cipherMode: cipher_mode_buf, + hashSpec: hash_spec_buf, + payloadOffset: payload_offset, + keyBytes: key_bytes, + mkDigest: mk_digest_buf, + mkDigestSalt: mk_digest_salt_buf, + mkDigestIterations: mk_digest_iterations, + uuid: uuid_buf, + }; + + Ok(res) + } + } + + fn u8_buf_to_str(buf: &[u8]) -> Result<&str, super::Error> { + if let Some(pos) = buf.iter().position(|&c| c == 0) { + str::from_utf8(&buf[0..pos]).map_err(From::from) + } else { + str::from_utf8(buf).map_err(From::from) + } + } + + impl super::LuksHeader for luks_phdr { + fn version(&self) -> u16 { + self.version + } + + fn cipher_name(&self) -> Result<&str, super::Error> { + u8_buf_to_str(&self.cipherName) + } + + fn cipher_mode(&self) -> Result<&str, super::Error> { + u8_buf_to_str(&self.cipherMode) + } + + fn hash_spec(&self) -> Result<&str, super::Error> { + u8_buf_to_str(&self.hashSpec) + } + + fn payload_offset(&self) -> u32 { + self.payloadOffset + } + + fn key_bytes(&self) -> u32 { + self.keyBytes + } + + fn mk_digest(&self) -> &[u8] { + &self.mkDigest + } + + fn mk_digest_salt(&self) -> &[u8] { + &self.mkDigestSalt + } + + fn mk_digest_iterations(&self) -> u32 { + self.mkDigestIterations + } + + fn uuid(&self) -> Result { + let uuid_str = try!(u8_buf_to_str(&self.uuid)); + uuid::Uuid::parse_str(uuid_str).map_err(From::from) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + use uuid; + + #[test] + fn test_luks_header_from_byte_buffer() { + let header = b"LUKS\xba\xbe\x00\x01aes\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ecb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00sha256\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00 \xcf^\xb4\xc00q\xbe\xd5\xe6\x90\xc8G\xb3\x00\xbe\xba\xd052qp\x92\x0c\x9c\xa9\x07R\\y_D\x08b\xf1\xe6\x8f\x0c\xa95\xad\xdb\x15+\xa5\xd7\xa7\xbf^\x96B\x90z\x00\x00\x03\xe8a1b49d2d-8a7e-4b04-ab2a-89f3408fd198\x00\x00\x00\x00"; + let mut cursor: Cursor<&[u8]> = Cursor::new(header); + let luks_header = BlockDevice::read_luks_header(&mut cursor).unwrap(); + + assert_eq!(luks_header.version(), 1); + assert_eq!(luks_header.cipher_name().unwrap(), "aes"); + assert_eq!(luks_header.cipher_mode().unwrap(), "ecb"); + assert_eq!(luks_header.hash_spec().unwrap(), "sha256"); + assert_eq!(luks_header.payload_offset(), 4096); + assert_eq!(luks_header.key_bytes(), 32); + assert_eq!( + luks_header.mk_digest(), + &[ + 207, 94, 180, 192, 48, 113, 190, 213, 230, 144, 200, 71, 179, 0, 190, 186, 208, 53, + 50, 113 + ] + ); + assert_eq!( + luks_header.mk_digest_salt(), + &[ + 112, 146, 12, 156, 169, 7, 82, 92, 121, 95, 68, 8, 98, 241, 230, 143, 12, 169, 53, + 173, 219, 21, 43, 165, 215, 167, 191, 94, 150, 66, 144, 122 + ] + ); + assert_eq!(luks_header.mk_digest_iterations(), 1000); + assert_eq!( + luks_header.uuid().unwrap(), + uuid::Uuid::parse_str("a1b49d2d-8a7e-4b04-ab2a-89f3408fd198").unwrap() + ) + } +} diff --git a/patch/cryptsetup-rs/examples/luks_dump.rs b/patch/cryptsetup-rs/examples/luks_dump.rs new file mode 100644 index 0000000..a7d21d1 --- /dev/null +++ b/patch/cryptsetup-rs/examples/luks_dump.rs @@ -0,0 +1,78 @@ +#![deny(warnings)] +#[warn(unused_must_use)] +extern crate cryptsetup_rs; +extern crate env_logger; + +use cryptsetup_rs::*; +use std::env; + +fn dump_slot(crypt_device: &Luks1CryptDevice, slot: Keyslot) -> Result<()> { + let status = match crypt_device.keyslot_status(slot) { + crypt_keyslot_info::CRYPT_SLOT_INVALID => "INVALID", + crypt_keyslot_info::CRYPT_SLOT_INACTIVE => "DISABLED", + crypt_keyslot_info::CRYPT_SLOT_ACTIVE | crypt_keyslot_info::CRYPT_SLOT_ACTIVE_LAST => "ENABLED", + }; + + println!("Key Slot {}: {}", slot, status); + match status { + "ENABLED" => /* TODO add keyslot information */ (), + _ => (), + } + Ok(()) +} + +fn dump(device_path: &str) -> Result<()> { + let dev = open(device_path)?.luks1()?; + + println!("LUKS header information for {}", dev.device_name()); + println!(); + println!("{:<16}{}", "Version:", "1"); + println!("{:<16}{}", "Cipher name:", dev.cipher()); + println!("{:<16}{}", "Cipher mode:", dev.cipher_mode()); + println!("{:<16}{}", "Hash spec:", dev.hash_spec()); + println!("{:<16}{}", "Payload offset:", dev.payload_offset()); + println!("{:<16}{}", "MK bits:", dev.mk_bits()); + + print!("{:<16}", "MK digest:"); + for b in dev.mk_digest().iter() { + print!("{:x} ", b); + } + println!(); + + let salt = dev.mk_salt(); + print!("{:<16}", "MK salt:"); + for b in salt[0..16].iter() { + print!("{:x} ", b); + } + println!(); + print!("{:<16}", ""); + for b in salt[16..32].iter() { + print!("{:x} ", b); + } + println!(); + + println!("{:<16}{}", "MK iterations:", dev.mk_iterations()); + println!("{:<16}{}", "UUID:", dev.uuid()); + + println!(); + + for slot in 0..8 { + dump_slot(&dev, slot)?; + } + + Ok(()) +} + +fn main() { + let args: Vec = env::args().skip(1).collect(); + if args.len() != 1 { + println!("Usage: luks_dump "); + ::std::process::exit(1); + } + let device_path = args[0].as_str(); + + if let Err(e) = dump(device_path) { + println!("Error: {:?}", e); + ::std::process::exit(2); + } +} diff --git a/patch/cryptsetup-rs/libcryptsetup-sys/Cargo.toml b/patch/cryptsetup-rs/libcryptsetup-sys/Cargo.toml new file mode 100644 index 0000000..378e073 --- /dev/null +++ b/patch/cryptsetup-rs/libcryptsetup-sys/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "libcryptsetup-sys" +version = "0.1.1" +authors = ["Vladimir Lushnikov "] +license = "LGPL-3.0" +description = "FFI bindings to the libcryptsetup library" +homepage = "https://github.com/solidninja/cryptsetup-rs" + +links = "cryptsetup" +build = "build.rs" + +[lib] +name = "libcryptsetup_sys" +path = "lib.rs" + +[dependencies] +libc = "0.2" + +[build-dependencies] +pkg-config = "0.3" + diff --git a/patch/cryptsetup-rs/libcryptsetup-sys/build.rs b/patch/cryptsetup-rs/libcryptsetup-sys/build.rs new file mode 100644 index 0000000..0b6d698 --- /dev/null +++ b/patch/cryptsetup-rs/libcryptsetup-sys/build.rs @@ -0,0 +1,8 @@ +extern crate pkg_config; + +fn main() { + pkg_config::Config::new() + .statik(true) + .find("libcryptsetup") + .unwrap(); +} diff --git a/patch/cryptsetup-rs/libcryptsetup-sys/lib.rs b/patch/cryptsetup-rs/libcryptsetup-sys/lib.rs new file mode 100644 index 0000000..2410afe --- /dev/null +++ b/patch/cryptsetup-rs/libcryptsetup-sys/lib.rs @@ -0,0 +1,492 @@ +#![deny(warnings)] +#![allow(non_camel_case_types)] +extern crate libc; + +use libc::{c_char, c_double, c_int, c_uint, c_void, size_t}; +use std::str::FromStr; + +pub enum crypt_device {} + +#[repr(i32)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_log_level { + CRYPT_LOG_NORMAL = 0, + CRYPT_LOG_ERROR = 1, + CRYPT_LOG_VERBOSE = 2, + CRYPT_LOG_DEBUG = -1, +} + +pub type crypt_log_cb = extern "C" fn(crypt_log_level, *const c_char, *mut c_void); +pub type crypt_confirm_cb = extern "C" fn(*const c_char, *mut c_void) -> c_int; +pub type crypt_password_cb = + extern "C" fn(*const c_char, *mut c_char, size_t, *mut c_void) -> c_int; + +#[repr(i32)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_rng_type { + CRYPT_RNG_URANDOM = 0, + CRYPT_RNG_RANDOM = 1, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_device_type { + PLAIN, + LUKS1, + LOOPAES, + VERITY, + TCRYPT, +} + +#[repr(C)] +pub struct crypt_params_plain { + pub hash: *const c_char, + pub offset: u64, + pub skip: u64, + pub size: u64, +} + +#[repr(C)] +pub struct crypt_params_luks1 { + pub hash: *const c_char, + pub data_alignment: size_t, + pub data_device: *const c_char, +} + +#[repr(C)] +pub struct crypt_params_loopaes { + pub hash: *const c_char, + pub offset: u64, + pub skip: u64, +} + +#[repr(C)] +pub struct crypt_params_verity { + pub hash_name: *const c_char, + pub data_device: *const c_char, + pub hash_device: *const c_char, + pub salt: *const c_char, + pub salt_size: u32, + pub hash_type: u32, + pub data_block_size: u32, + pub hash_block_size: u32, + pub data_size: u64, + pub hash_area_offset: u64, + pub flags: u32, +} + +#[repr(u32)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_verity_flag { + CRYPT_VERITY_NO_HEADER = (1 << 0), + CRYPT_VERITY_CHECK_HASH = (1 << 1), + CRYPT_VERITY_CREATE_HASH = (1 << 2), +} + +#[repr(C)] +pub struct crypt_params_tcrypt { + pub passphrase: *const c_char, + pub passphrase_size: size_t, + pub keyfiles: *const *const c_char, + pub keyfiles_count: c_uint, + pub hash_name: *const c_char, + pub cipher: *const c_char, + pub mode: *const c_char, + pub key_size: size_t, + pub flags: u32, +} + +#[repr(u32)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_tcrypt_flag { + CRYPT_TCRYPT_LEGACY_MODES = (1 << 0), + CRYPT_TCRYPT_HIDDEN_HEADER = (1 << 1), + CRYPT_TCRYPT_BACKUP_HEADER = (1 << 2), + CRYPT_TCRYPT_SYSTEM_HEADER = (1 << 3), + CRYPT_TCRYPT_VERA_MODES = (1 << 4), +} + +pub const CRYPT_ANY_SLOT: c_int = -1; + +#[repr(u32)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_activation_flag { + CRYPT_ACTIVATE_READONLY = (1 << 0), + CRYPT_ACTIVATE_NO_UUID = (1 << 1), + CRYPT_ACTIVATE_SHARED = (1 << 2), + CRYPT_ACTIVATE_ALLOW_DISCARDS = (1 << 3), + CRYPT_ACTIVATE_PRIVATE = (1 << 4), + CRYPT_ACTIVATE_CORRUPTED = (1 << 5), + CRYPT_ACTIVATE_SAME_CPU_CRYPT = (1 << 6), + CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS = (1 << 7), + CRYPT_ACTIVATE_IGNORE_CORRUPTION = (1 << 8), + CRYPT_ACTIVATE_RESTART_ON_CORRUPTION = (1 << 9), + CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS = (1 << 10), +} + +#[repr(C)] +pub struct crypt_active_device { + pub offset: u64, + pub iv_offset: u64, + pub size: u64, + pub flags: u32, +} + +#[repr(C)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_status_info { + CRYPT_INVALID, + CRYPT_INACTIVE, + CRYPT_ACTIVE, + CRYPT_BUSY, +} + +#[repr(C)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_keyslot_info { + CRYPT_SLOT_INVALID, + CRYPT_SLOT_INACTIVE, + CRYPT_SLOT_ACTIVE, + CRYPT_SLOT_ACTIVE_LAST, +} + +#[repr(C)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum crypt_debug_level { + CRYPT_DEBUG_ALL = -1, + CRYPT_DEBUG_NONE = 0, +} + +extern "C" { + pub fn crypt_init(cd: *mut *mut crypt_device, device: *const c_char) -> c_int; + pub fn crypt_init_by_name_and_header( + cd: *mut *mut crypt_device, + name: *const c_char, + header_device: *const c_char, + ) -> c_int; + pub fn crypt_init_by_name(cd: *mut *mut crypt_device, name: *const c_char) -> c_int; + + pub fn crypt_set_log_callback( + cd: *mut crypt_device, + log: Option, + usrptr: *mut c_void, + ); + pub fn crypt_log(cd: *mut crypt_device, level: crypt_log_level, msg: *const c_char); + + pub fn crypt_set_confirm_callback( + cd: *mut crypt_device, + confirm: crypt_confirm_cb, + usrptr: *mut c_void, + ); + #[deprecated] + pub fn crypt_set_password_callback( + cd: *mut crypt_device, + password: crypt_password_cb, + usrptr: *mut c_void, + ); + #[deprecated] + pub fn crypt_set_timeout(cd: *mut crypt_device, timeout: u64); + #[deprecated] + pub fn crypt_set_password_retry(cd: *mut crypt_device, tries: c_int); + pub fn crypt_set_iteration_time(cd: *mut crypt_device, iteration_time_ms: u64); + #[deprecated] + pub fn crypt_set_password_verify(cd: *mut crypt_device, password_verify: c_int); + pub fn crypt_set_data_device(cd: *mut crypt_device, device: *const c_char) -> c_int; + + pub fn crypt_set_rng_type(cd: *mut crypt_device, rng_type: crypt_rng_type); + pub fn crypt_get_rng_type(cd: *mut crypt_device) -> c_int; + + pub fn crypt_memory_lock(cd: *mut crypt_device, lock: c_int) -> c_int; + + pub fn crypt_get_type(cd: *mut crypt_device) -> *const c_char; + + pub fn crypt_format( + cd: *mut crypt_device, + crypt_type: *const c_char, + cipher: *const c_char, + cipher_mode: *const c_char, + uuid: *const c_char, + volume_key: *const c_char, + volume_key_size: size_t, + params: *mut c_void, + ) -> c_int; + + pub fn crypt_set_uuid(cd: *mut crypt_device, uuid: *const c_char) -> c_int; + + pub fn crypt_load( + cd: *mut crypt_device, + requested_type: *const c_char, + params: *mut c_void, + ) -> c_int; + + pub fn crypt_repair( + cd: *mut crypt_device, + requested_type: *const c_char, + params: *mut c_void, + ) -> c_int; + + pub fn crypt_resize(cd: *mut crypt_device, name: *const c_char, new_size: u64) -> c_int; + + pub fn crypt_suspend(cd: *mut crypt_device, name: *const c_char) -> c_int; + + pub fn crypt_resume_by_passphrase( + cd: *mut crypt_device, + name: *const c_char, + keyslot: c_int, + passphrase: *const c_char, + passphrase_size: size_t, + ) -> c_int; + pub fn crypt_resume_by_keyfile_offset( + cd: *mut crypt_device, + name: *const c_char, + keyslot: c_int, + keyfile: *const c_char, + keyfile_size: size_t, + keyfile_offset: size_t, + ) -> c_int; + pub fn crypt_resume_by_keyfile( + cd: *mut crypt_device, + name: *const c_char, + keyslot: c_int, + keyfile: *const c_char, + keyfile_size: size_t, + ) -> c_int; + + pub fn crypt_free(cd: *mut crypt_device); + + pub fn crypt_keyslot_add_by_passphrase( + cd: *mut crypt_device, + keyslot: c_int, + passphrase: *const c_char, + passphrase_size: size_t, + new_passphrase: *const c_char, + new_passphrase_size: size_t, + ) -> c_int; + pub fn crypt_keyslot_change_by_passphrase( + cd: *mut crypt_device, + keyslot_old: c_int, + keyslot_new: c_int, + passphrase: *const c_char, + passphrase_size: size_t, + new_passphrase: *const c_char, + new_passphrase_size: size_t, + ) -> c_int; + + pub fn crypt_keyslot_add_by_keyfile_offset( + cd: *mut crypt_device, + keyslot: c_int, + keyfile: *const c_char, + keyfile_size: size_t, + keyfile_offset: size_t, + new_keyfile: *const c_char, + new_keyfile_size: size_t, + new_keyfile_offset: size_t, + ) -> c_int; + pub fn crypt_keyslot_add_by_keyfile( + cd: *mut crypt_device, + keyslot: c_int, + keyfile: *const c_char, + keyfile_size: size_t, + new_keyfile: *const c_char, + new_keyfile_size: size_t, + ) -> c_int; + + pub fn crypt_keyslot_add_by_volume_key( + cd: *mut crypt_device, + keyslot: c_int, + volume_key: *const c_char, + volume_key_size: size_t, + passphrase: *const c_char, + passphrase_size: size_t, + ) -> c_int; + + pub fn crypt_keyslot_destroy(cd: *mut crypt_device, keyslot: c_int) -> c_int; + + pub fn crypt_get_active_device( + cd: *mut crypt_device, + name: *const c_char, + cad: *mut crypt_active_device, + ) -> c_int; + + pub fn crypt_activate_by_passphrase( + cd: *mut crypt_device, + name: *const c_char, + keyslot: c_int, + passphrase: *const c_char, + passphrase_size: size_t, + flags: u32, + ) -> c_int; + + pub fn crypt_activate_by_keyfile_offset( + cd: *mut crypt_device, + name: *const c_char, + keyslot: c_int, + keyfile: *const c_char, + keyfile_size: size_t, + keyfile_offset: size_t, + flags: u32, + ) -> c_int; + pub fn crypt_activate_by_keyfile( + cd: *mut crypt_device, + name: *const c_char, + keyslot: c_int, + keyfile: *const c_char, + keyfile_size: size_t, + flags: u32, + ) -> c_int; + + pub fn crypt_activate_by_volume_key( + cd: *mut crypt_device, + name: *const c_char, + volume_key: *const c_char, + volume_key_size: size_t, + flags: u32, + ) -> c_int; + + pub fn crypt_deactivate(cd: *mut crypt_device, name: *const c_char) -> c_int; + + pub fn crypt_volume_key_get( + cd: *mut crypt_device, + keyslot: c_int, + volume_key: *mut c_char, + volume_key_size: *mut size_t, + passphrase: *const c_char, + passphrase_size: size_t, + ) -> c_int; + + pub fn crypt_volume_key_verify( + cd: *mut crypt_device, + volume_key: *const c_char, + volume_key_size: size_t, + ) -> c_int; + + pub fn crypt_status(cd: *mut crypt_device, name: *const c_char) -> crypt_status_info; + + pub fn crypt_dump(cd: *mut crypt_device) -> c_int; + + pub fn crypt_get_cipher(cd: *mut crypt_device) -> *const c_char; + pub fn crypt_get_cipher_mode(cd: *mut crypt_device) -> *const c_char; + pub fn crypt_get_uuid(cd: *mut crypt_device) -> *const c_char; + pub fn crypt_get_device_name(cd: *mut crypt_device) -> *const c_char; + pub fn crypt_get_data_offset(cd: *mut crypt_device) -> u64; + pub fn crypt_get_iv_offset(cd: *mut crypt_device) -> u64; + pub fn crypt_get_volume_key_size(cd: *mut crypt_device) -> c_int; + pub fn crypt_get_verity_info(cd: *mut crypt_device, vp: *mut crypt_params_verity); + + pub fn crypt_benchmark( + cd: *mut crypt_device, + cipher: *const c_char, + cipher_mode: *const c_char, + volume_key_size: size_t, + iv_size: size_t, + buffer_size: size_t, + encryption_mbs: *mut c_double, + decryption_mbs: *mut c_double, + ) -> c_int; + pub fn crypt_benchmark_kdf( + cd: *mut crypt_device, + kdf: *const c_char, + hash: *const c_char, + password: *const c_char, + password_size: size_t, + salt: *const c_char, + salt_size: size_t, + iterations_sec: *mut u64, + ) -> c_int; + + pub fn crypt_keyslot_status(cd: *mut crypt_device, keyslot: c_int) -> crypt_keyslot_info; + + pub fn crypt_keyslot_max(crypt_device_type: *const c_char) -> c_int; + + pub fn crypt_keyslot_area( + cd: *mut crypt_device, + keyslot: c_int, + offset: *mut u64, + length: *mut u64, + ) -> c_int; + + pub fn crypt_header_backup( + cd: *mut crypt_device, + requested_type: *const c_char, + backup_file: *const c_char, + ) -> c_int; + pub fn crypt_header_restore( + cd: *mut crypt_device, + requested_type: *const c_char, + backup_file: *const c_char, + ) -> c_int; + + #[deprecated] + pub fn crypt_last_error(cd: *mut crypt_device, buf: *mut c_char, size: size_t); + #[deprecated] + pub fn crypt_get_error(buf: *mut c_char, size: size_t); + + pub fn crypt_get_dir() -> *const c_char; + + pub fn crypt_set_debug_level(level: crypt_debug_level); +} + +impl FromStr for crypt_device_type { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "PLAIN" => Ok(crypt_device_type::PLAIN), + "LUKS1" => Ok(crypt_device_type::LUKS1), + "LOOPAES" => Ok(crypt_device_type::LOOPAES), + "VERITY" => Ok(crypt_device_type::VERITY), + "TCRYPT" => Ok(crypt_device_type::TCRYPT), + _ => Err(()), + } + } +} + +impl crypt_device_type { + pub fn to_str(&self) -> &str { + match self { + &crypt_device_type::PLAIN => "PLAIN", + &crypt_device_type::LUKS1 => "LUKS1", + &crypt_device_type::LOOPAES => "LOOPAES", + &crypt_device_type::VERITY => "VERITY", + &crypt_device_type::TCRYPT => "TCRYPT", + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + use std::str::FromStr; + + #[test] + fn test_device_type_conversion() { + assert_eq!( + Ok(crypt_device_type::PLAIN), + crypt_device_type::from_str("PLAIN") + ); + assert_eq!( + Ok(crypt_device_type::LUKS1), + crypt_device_type::from_str("LUKS1") + ); + assert_eq!( + Ok(crypt_device_type::LOOPAES), + crypt_device_type::from_str("LOOPAES") + ); + assert_eq!( + Ok(crypt_device_type::VERITY), + crypt_device_type::from_str("VERITY") + ); + assert_eq!( + Ok(crypt_device_type::TCRYPT), + crypt_device_type::from_str("TCRYPT") + ); + } + + #[test] + fn test_keyslot_max_gt_zero() { + unsafe { + let luks_type = CString::new("LUKS1").unwrap(); + assert!(crypt_keyslot_max(luks_type.as_ptr()) > 0); + } + } +} diff --git a/patch/cryptsetup-rs/rustfmt.toml b/patch/cryptsetup-rs/rustfmt.toml new file mode 100644 index 0000000..a125226 --- /dev/null +++ b/patch/cryptsetup-rs/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 120 +format_strings = false diff --git a/patch/cryptsetup-rs/src/api.rs b/patch/cryptsetup-rs/src/api.rs new file mode 100644 index 0000000..bfb7aa4 --- /dev/null +++ b/patch/cryptsetup-rs/src/api.rs @@ -0,0 +1,377 @@ +//! High-level API to work with `libcryptsetup` supported devices (disks) + +use std::fmt; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::ptr; + +use blkid_rs::{BlockDevice, LuksHeader}; + +use device; +pub use device::enable_debug; +use device::RawDevice; +pub use device::{Error, Keyslot, Result}; +use raw; +use uuid; + +pub type Luks1CryptDeviceHandle = CryptDeviceHandle; + +/// Builder to open a crypt device at the specified path +/// +/// # Examples +/// +/// ``` +/// use cryptsetup_rs::*; +/// # fn foo() -> Result<()> { +/// let device = open("/dev/loop0")?.luks1()?; +/// # Ok(()) +/// # } +/// ``` +pub fn open>(path: P) -> Result { + let cd = device::init(path.as_ref())?; + Ok(CryptDeviceOpenBuilder { + path: path.as_ref().to_owned(), + cd, + }) +} + +/// Builder to format a crypt device at the specified path +/// +/// # Examples +/// +/// ``` +/// # extern crate uuid; +/// # extern crate cryptsetup_rs; +/// use cryptsetup_rs::*; +/// use uuid::Uuid; +/// +/// # fn foo() -> Result<()> { +/// let uuid = Uuid::new_v4(); +/// let device = format("/dev/loop0")? +/// .rng_type(crypt_rng_type::CRYPT_RNG_URANDOM) +/// .iteration_time(5000) +/// .luks1("aes", "xts-plain", "sha256", 256, Some(&uuid))?; +/// # Ok(()) +/// # } +/// ``` +pub fn format>(path: P) -> Result { + let cd = device::init(path.as_ref())?; + Ok(CryptDeviceFormatBuilder { + path: path.as_ref().to_owned(), + cd, + }) +} + +/// Read the UUID of a LUKS1 container without opening the device +pub fn luks1_uuid>(path: P) -> Result { + let device_file = File::open(path.as_ref())?; + let luks_phdr = BlockDevice::read_luks_header(device_file)?; + let uuid = luks_phdr.uuid()?; + Ok(uuid) +} + +fn load_luks1_params>(path: P) -> Result { + let device_file = File::open(path.as_ref())?; + let luks_phdr = BlockDevice::read_luks_header(device_file)?; + Luks1Params::from(luks_phdr) +} + +/// Struct containing state for the `open()` builder +pub struct CryptDeviceOpenBuilder { + path: PathBuf, + cd: RawDevice, +} + +impl CryptDeviceOpenBuilder { + /// Loads an existing LUKS1 crypt device + pub fn luks1(self: CryptDeviceOpenBuilder) -> Result> { + let _ = device::load(&self.cd, raw::crypt_device_type::LUKS1); + let params = load_luks1_params(&self.path)?; + Ok(CryptDeviceHandle { + cd: self.cd, + path: self.path, + params, + }) + } +} + +/// Struct containing state for the `format()` builder +pub struct CryptDeviceFormatBuilder { + path: PathBuf, + cd: RawDevice, +} + +impl CryptDeviceFormatBuilder { + /// Set the iteration time for the `PBKDF2` function. Note that this does not affect the MK iterations. + pub fn iteration_time(mut self, iteration_time_ms: u64) -> Self { + device::set_iteration_time(&mut self.cd, iteration_time_ms); + self + } + + /// Set the random number generator to use + pub fn rng_type(mut self, rng_type: raw::crypt_rng_type) -> Self { + device::set_rng_type(&mut self.cd, rng_type); + self + } + + /// Formats a new block device as a LUKS1 crypt device with the specified parameters + pub fn luks1( + mut self: CryptDeviceFormatBuilder, + cipher: &str, + cipher_mode: &str, + hash: &str, + mk_bits: usize, + maybe_uuid: Option<&uuid::Uuid>, + ) -> Result> { + let _ = device::luks1_format(&mut self.cd, cipher, cipher_mode, hash, mk_bits, maybe_uuid)?; + let params = load_luks1_params(&self.path)?; + Ok(CryptDeviceHandle { + cd: self.cd, + path: self.path, + params, + }) + } +} + +/// Trait representing common operations on a crypt device +pub trait CryptDevice { + /// Path the device was opened/created with + fn path(&self) -> &Path; + + /// Name of cipher used + fn cipher(&self) -> &str; + + /// Name of cipher mode used + fn cipher_mode(&self) -> &str; + + /// Path to the underlying device (as reported by `libcryptsetup`) + fn device_name(&self) -> &str; + + /// Random number generator used for operations on this crypt device + fn rng_type(&self) -> raw::crypt_rng_type; + + /// Sets the random number generator to use + fn set_rng_type(&mut self, rng_type: raw::crypt_rng_type); + + /// Sets the iteration time for the `PBKDF2` function. Note that this does not affect the MK iterations. + fn set_iteration_time(&mut self, iteration_time_ms: u64); + + /// Volume key size (in bytes) + fn volume_key_size(&self) -> u8; +} + +/// Trait for querying the device type at runtime +pub trait CryptDeviceType { + /// Type of the crypt device + fn device_type(&self) -> raw::crypt_device_type; +} + +/// Trait representing specific operations on a LUKS1 device +pub trait Luks1CryptDevice { + /// Activate the crypt device, and give it the specified name + fn activate(&mut self, name: &str, key: &[u8]) -> Result; + + /// Add a new keyslot with the specified key + fn add_keyslot( + &mut self, + key: &[u8], + maybe_prev_key: Option<&[u8]>, + maybe_keyslot: Option, + ) -> Result; + + /// Replace an old key with a new one + fn update_keyslot(&mut self, key: &[u8], prev_key: &[u8], maybe_keyslot: Option) -> Result; + + /// Destroy (and disable) key slot + fn destroy_keyslot(&mut self, slot: Keyslot) -> Result<()>; + + /// Dump text-formatted information about the current device to stdout + fn dump(&self); + + /// Get the hash algorithm used + fn hash_spec(&self) -> &str; + + /// Get status of key slot + fn keyslot_status(&self, keyslot: Keyslot) -> raw::crypt_keyslot_info; + + /// Number of bits in the master key + fn mk_bits(&self) -> u32; + + /// Master key header digest + fn mk_digest(&self) -> &[u8; 20]; + + /// Master key `PBKDF2` iterations + fn mk_iterations(&self) -> u32; + + /// Master key salt + fn mk_salt(&self) -> &[u8; 32]; + + /// Get the offset of the payload + fn payload_offset(&self) -> u32; + + /// UUID of the current device + fn uuid(&self) -> uuid::Uuid; +} + +/// An opaque handle on an initialized crypt device +#[derive(PartialEq)] +pub struct CryptDeviceHandle { + /// Pointer to the raw device + cd: RawDevice, + + /// Path to the crypt device (useful for diagnostics) + path: PathBuf, + + /// Additional parameters depending on type of crypt device opened + params: P, +} + +impl fmt::Debug for CryptDeviceHandle

{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "CryptDeviceHandle(path={}, raw={:p}, params={:?})", + self.path.display(), + self.cd, + self.params + ) + } +} + +impl Drop for CryptDeviceHandle

{ + fn drop(&mut self) { + device::free(&mut self.cd); + self.cd = ptr::null_mut(); + } +} + +impl CryptDevice for CryptDeviceHandle

{ + fn path(&self) -> &Path { + self.path.as_ref() + } + + fn cipher(&self) -> &str { + device::cipher(&self.cd).expect("Initialised device should have cipher") + } + + fn cipher_mode(&self) -> &str { + device::cipher_mode(&self.cd).expect("Initialised device should have cipher mode") + } + + fn device_name(&self) -> &str { + device::device_name(&self.cd).expect("Initialised device should have an underlying path") + } + + fn rng_type(&self) -> raw::crypt_rng_type { + device::rng_type(&self.cd) + } + + fn set_rng_type(&mut self, rng_type: raw::crypt_rng_type) { + device::set_rng_type(&mut self.cd, rng_type) + } + + fn set_iteration_time(&mut self, iteration_time_ms: u64) { + device::set_iteration_time(&mut self.cd, iteration_time_ms) + } + + fn volume_key_size(&self) -> u8 { + device::volume_key_size(&self.cd) + } +} + +/// Struct for storing LUKS1 parameters in memory +#[derive(Debug, PartialEq)] +pub struct Luks1Params { + hash_spec: String, + payload_offset: u32, + mk_bits: u32, + mk_digest: [u8; 20], + mk_salt: [u8; 32], + mk_iterations: u32, +} + +impl Luks1Params { + fn from(header: impl LuksHeader) -> Result { + let hash_spec = header.hash_spec()?.to_owned(); + let payload_offset = header.payload_offset(); + let mk_bits = header.key_bytes() * 8; + let mut mk_digest = [0u8; 20]; + mk_digest.copy_from_slice(header.mk_digest()); + let mut mk_salt = [0u8; 32]; + mk_salt.copy_from_slice(header.mk_digest_salt()); + let mk_iterations = header.mk_digest_iterations(); + Ok(Luks1Params { + hash_spec, + payload_offset, + mk_bits, + mk_digest, + mk_salt, + mk_iterations, + }) + } +} + +impl Luks1CryptDevice for CryptDeviceHandle { + fn activate(&mut self, name: &str, key: &[u8]) -> Result { + device::luks_activate(&mut self.cd, name, key) + } + + fn add_keyslot( + &mut self, + key: &[u8], + maybe_prev_key: Option<&[u8]>, + maybe_keyslot: Option, + ) -> Result { + device::luks_add_keyslot(&mut self.cd, key, maybe_prev_key, maybe_keyslot) + } + + fn update_keyslot(&mut self, key: &[u8], prev_key: &[u8], maybe_keyslot: Option) -> Result { + device::luks_update_keyslot(&mut self.cd, key, prev_key, maybe_keyslot) + } + + fn destroy_keyslot(&mut self, slot: Keyslot) -> Result<()> { + device::luks_destroy_keyslot(&mut self.cd, slot) + } + + fn dump(&self) { + device::dump(&self.cd).expect("Dump should be fine for initialised device") + } + + fn hash_spec(&self) -> &str { + self.params.hash_spec.as_ref() + } + + fn keyslot_status(&self, keyslot: Keyslot) -> raw::crypt_keyslot_info { + device::keyslot_status(&self.cd, keyslot) + } + + fn mk_bits(&self) -> u32 { + self.params.mk_bits + } + + fn mk_digest(&self) -> &[u8; 20] { + &self.params.mk_digest + } + + fn mk_iterations(&self) -> u32 { + self.params.mk_iterations + } + + fn mk_salt(&self) -> &[u8; 32] { + &self.params.mk_salt + } + + fn payload_offset(&self) -> u32 { + self.params.payload_offset + } + + fn uuid(&self) -> uuid::Uuid { + device::uuid(&self.cd).expect("LUKS1 device should have UUID") + } +} + +impl CryptDeviceType for CryptDeviceHandle { + fn device_type(&self) -> raw::crypt_device_type { + raw::crypt_device_type::LUKS1 + } +} diff --git a/patch/cryptsetup-rs/src/device.rs b/patch/cryptsetup-rs/src/device.rs new file mode 100644 index 0000000..87d2958 --- /dev/null +++ b/patch/cryptsetup-rs/src/device.rs @@ -0,0 +1,319 @@ +//! Low-level cryptsetup binding that sits directly on top of the `libcryptsetup` C API +//! +//! Consider using the high-level binding in the `api` module instead + +use std::ffi; +use std::mem; +use std::path::Path; +use std::ptr; +use std::result; +use std::str; + +use blkid_rs; +use errno; +use libc; +use raw; +use uuid; + +/// Raw pointer to the underlying `crypt_device` opaque struct +pub type RawDevice = *mut raw::crypt_device; + +#[derive(Debug)] +pub enum Error { + /// Error that originates from `libcryptsetup` (with numeric error code) + CryptsetupError(errno::Errno), + /// IO error + IOError(::std::io::Error), + /// Error from the blkid-rs library (while reading LUKS1 header) + BlkidError(blkid_rs::Error), +} + +impl From<::std::io::Error> for Error { + fn from(e: ::std::io::Error) -> Self { + Error::IOError(e) + } +} + +impl From for Error { + fn from(e: blkid_rs::Error) -> Self { + Error::BlkidError(e) + } +} + +pub type Result = result::Result; +pub type Keyslot = u8; + +const ANY_KEYSLOT: libc::c_int = -1 as libc::c_int; + +fn str_from_c_str<'a>(c_str: *const libc::c_char) -> Option<&'a str> { + if c_str.is_null() { + None + } else { + unsafe { Some(ffi::CStr::from_ptr(c_str).to_str().unwrap()) } + } +} + +macro_rules! crypt_error { + ($res:expr) => { + Err(Error::CryptsetupError(errno::Errno(-$res))) + }; +} + +macro_rules! check_crypt_error { + ($res:expr) => { + if $res != 0 { + crypt_error!($res) + } else { + Ok(()) + } + }; +} + +/// Log function callback used by `libcryptsetup` +#[allow(unused)] +#[no_mangle] +pub extern "C" fn cryptsetup_rs_log_callback( + level: raw::crypt_log_level, + message: *const libc::c_char, + usrptr: *mut libc::c_void, +) { + let msg = str_from_c_str(message).unwrap(); + match level { + raw::crypt_log_level::CRYPT_LOG_NORMAL => info!("{}", msg.trim_right()), + raw::crypt_log_level::CRYPT_LOG_ERROR => error!("{}", msg.trim_right()), + raw::crypt_log_level::CRYPT_LOG_VERBOSE => debug!("{}", msg.trim_right()), + raw::crypt_log_level::CRYPT_LOG_DEBUG => debug!("{}", msg.trim_right()), + } +} + +/// Enable internal `libcryptsetup` debugging +pub fn enable_debug(debug: bool) { + if debug { + unsafe { raw::crypt_set_debug_level(raw::crypt_debug_level::CRYPT_DEBUG_ALL) }; + } else { + unsafe { raw::crypt_set_debug_level(raw::crypt_debug_level::CRYPT_DEBUG_NONE) }; + } +} + +/// Initialise crypt device and check if provided device exists +pub fn init>(path: P) -> Result { + let mut cd = ptr::null_mut(); + let c_path = ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap(); + + let res = unsafe { raw::crypt_init(&mut cd as *mut *mut raw::crypt_device, c_path.as_ptr()) }; + + if res != 0 { + crypt_error!(res) + } else { + unsafe { + raw::crypt_set_log_callback(cd, Some(cryptsetup_rs_log_callback), ptr::null_mut()); + } + Ok(cd) + } +} + +/// Load crypt device parameters from the on-disk header +/// +/// Note that typically you cannot query the crypt device for information before this function is +/// called. +pub fn load(cd: &RawDevice, requested_type: raw::crypt_device_type) -> Result<()> { + let c_type = ffi::CString::new(requested_type.to_str()).unwrap(); + + let res = unsafe { raw::crypt_load(*cd, c_type.as_ptr(), ptr::null_mut()) }; + + check_crypt_error!(res) +} + +/// Get the cipher used by this crypt device +pub fn cipher<'a>(cd: &'a RawDevice) -> Option<&'a str> { + let c_cipher = unsafe { raw::crypt_get_cipher(*cd) }; + str_from_c_str(c_cipher) +} + +/// Get the cipher mode used by this crypt device +pub fn cipher_mode<'a>(cd: &'a RawDevice) -> Option<&'a str> { + let c_cipher_mode = unsafe { raw::crypt_get_cipher_mode(*cd) }; + str_from_c_str(c_cipher_mode) +} + +/// Get the path to the device (as `libcryptsetup` sees it) +pub fn device_name<'a>(cd: &'a RawDevice) -> Option<&'a str> { + let c_device_name = unsafe { raw::crypt_get_device_name(*cd) }; + str_from_c_str(c_device_name) +} + +/// Dump text-formatted information about this device to the console +pub fn dump(cd: &RawDevice) -> Result<()> { + let res = unsafe { raw::crypt_dump(*cd) }; + check_crypt_error!(res) +} + +/// Releases crypt device context and memory +pub fn free(cd: &mut RawDevice) { + unsafe { raw::crypt_free(*cd) } +} + +/// Activate device based on provided key ("passphrase") +pub fn luks_activate(cd: &mut RawDevice, name: &str, key: &[u8]) -> Result { + let c_name = ffi::CString::new(name).unwrap(); + let c_passphrase_len = key.len() as libc::size_t; + // cast the passphrase to a pointer directly - it will not be NUL terminated but the passed length is used + let c_passphrase = key as *const [u8] as *const libc::c_char; + + let res = unsafe { + raw::crypt_activate_by_passphrase(*cd, c_name.as_ptr(), ANY_KEYSLOT, c_passphrase, c_passphrase_len, 0u32) + }; + + if res < 0 { + crypt_error!(res) + } else { + Ok(res as u8) + } +} + +/// Add key slot using provided passphrase. If there is no previous passphrase, use the volume key +/// that is in-memory to add the new key slot. +pub fn luks_add_keyslot( + cd: &mut RawDevice, + key: &[u8], + maybe_prev_key: Option<&[u8]>, + maybe_keyslot: Option, +) -> Result { + let c_key_len = key.len() as libc::size_t; + let c_key = key as *const [u8] as *const libc::c_char;; + let c_keyslot = maybe_keyslot + .map(|k| k as libc::c_int) + .unwrap_or(ANY_KEYSLOT as libc::c_int); + + let res = if let Some(prev_key) = maybe_prev_key { + let c_prev_key_len = prev_key.len() as libc::size_t; + let c_prev_key = prev_key as *const [u8] as *const libc::c_char;; + + unsafe { raw::crypt_keyslot_add_by_passphrase(*cd, c_keyslot, c_prev_key, c_prev_key_len, c_key, c_key_len) } + } else { + unsafe { + raw::crypt_keyslot_add_by_volume_key(*cd, c_keyslot, ptr::null(), 0 as libc::size_t, c_key, c_key_len) + } + }; + + if res < 0 { + crypt_error!(res) + } else { + Ok(res as Keyslot) + } +} + +/// Add key slot using provided passphrase. If there is no previous passphrase, use the volume key +/// that is in-memory to add the new key slot. +pub fn luks_update_keyslot( + cd: &mut RawDevice, + key: &[u8], + prev_key: &[u8], + maybe_keyslot: Option, +) -> Result { + let c_key_len = key.len() as libc::size_t; + let c_key = key as *const [u8] as *const libc::c_char;; + let c_keyslot = maybe_keyslot + .map(|k| k as libc::c_int) + .unwrap_or(ANY_KEYSLOT as libc::c_int); + + let c_prev_key_len = prev_key.len() as libc::size_t; + let c_prev_key = prev_key as *const [u8] as *const libc::c_char;; + + let res = unsafe { + raw::crypt_keyslot_change_by_passphrase(*cd, c_keyslot, c_keyslot, c_prev_key, c_prev_key_len, c_key, c_key_len) + }; + + if res < 0 { + crypt_error!(res) + } else { + Ok(res as Keyslot) + } +} + +/// Destroy (and disable) key slot +pub fn luks_destroy_keyslot(cd: &mut RawDevice, keyslot: Keyslot) -> Result<()> { + let res = unsafe { raw::crypt_keyslot_destroy(*cd, keyslot as libc::c_int) }; + if res < 0 { + crypt_error!(res) + } else { + Ok(()) + } +} + +/// Format a new crypt device but do not activate it +/// +/// Note this does not add an active keyslot +pub fn luks1_format( + cd: &mut RawDevice, + cipher: &str, + cipher_mode: &str, + hash: &str, + mk_bits: usize, + maybe_uuid: Option<&uuid::Uuid>, +) -> Result<()> { + let c_cipher = ffi::CString::new(cipher).unwrap(); + let c_cipher_mode = ffi::CString::new(cipher_mode).unwrap(); + let c_hash = ffi::CString::new(hash).unwrap(); + let c_uuid = maybe_uuid.map(|uuid| ffi::CString::new(uuid.hyphenated().to_string()).unwrap()); + + let mut luks_params = raw::crypt_params_luks1 { + hash: c_hash.as_ptr(), + data_alignment: 0, + data_device: ptr::null(), + }; + let c_luks_params: *mut raw::crypt_params_luks1 = &mut luks_params; + let c_luks_type = ffi::CString::new(raw::crypt_device_type::LUKS1.to_str()).unwrap(); + let c_uuid_ptr = c_uuid.as_ref().map(|u| u.as_ptr()).unwrap_or(ptr::null()); + let res = unsafe { + raw::crypt_format( + *cd, + c_luks_type.as_ptr(), + c_cipher.as_ptr(), + c_cipher_mode.as_ptr(), + c_uuid_ptr, + ptr::null(), + mk_bits / 8, + c_luks_params as *mut libc::c_void, + ) + }; + + check_crypt_error!(res) +} + +/// Get which RNG is used +pub fn rng_type(cd: &RawDevice) -> raw::crypt_rng_type { + unsafe { + let res = raw::crypt_get_rng_type(*cd); + mem::transmute(res) + } +} + +/// Set the number of milliseconds for `PBKDF2` function iteration +pub fn set_iteration_time(cd: &mut RawDevice, iteration_time_ms: u64) { + unsafe { + raw::crypt_set_iteration_time(*cd, iteration_time_ms); + } +} + +/// Set which RNG is used +pub fn set_rng_type(cd: &mut RawDevice, rng_type: raw::crypt_rng_type) { + unsafe { raw::crypt_set_rng_type(*cd, rng_type) } +} + +/// Get information about a keyslot +pub fn keyslot_status(cd: &RawDevice, slot: Keyslot) -> raw::crypt_keyslot_info { + unsafe { raw::crypt_keyslot_status(*cd, slot as libc::c_int) } +} + +/// Get size in bytes of the volume key +pub fn volume_key_size(cd: &RawDevice) -> u8 { + let res = unsafe { raw::crypt_get_volume_key_size(*cd) }; + res as u8 +} + +/// Get device UUID +pub fn uuid<'a>(cd: &'a RawDevice) -> Option { + let c_uuid_str = unsafe { raw::crypt_get_uuid(*cd) }; + str_from_c_str(c_uuid_str).and_then(|uuid_str| uuid::Uuid::parse_str(uuid_str).ok()) +} diff --git a/patch/cryptsetup-rs/src/lib.rs b/patch/cryptsetup-rs/src/lib.rs new file mode 100644 index 0000000..93b94cb --- /dev/null +++ b/patch/cryptsetup-rs/src/lib.rs @@ -0,0 +1,32 @@ +//! Rust bindings to `libcryptsetup` - working with encrypted disks on Linux +//! +//! # Example +//! +//! See `api` module documentation for more. +//! +//! ``` +//! use cryptsetup_rs::*; +//! # fn foo() -> Result<()> { +//! let device = open("/dev/loop0")?.luks1()?; +//! println!("Device UUID: {}", device.uuid()); +//! println!("Device cipher: {}", device.cipher()); +//! # Ok(()) +//! # } +//! ``` + +#[warn(unused_must_use)] +extern crate blkid_rs; +extern crate errno; +extern crate libc; +extern crate libcryptsetup_sys as raw; +extern crate uuid; + +#[macro_use] +extern crate log; + +pub mod api; +pub mod device; + +pub use api::{enable_debug, format, luks1_uuid, open}; +pub use api::{CryptDevice, CryptDeviceType, Error, Keyslot, Luks1CryptDevice, Luks1CryptDeviceHandle, Result}; +pub use raw::{crypt_device_type, crypt_keyslot_info, crypt_rng_type}; diff --git a/patch/cryptsetup-rs/tests/tests.rs b/patch/cryptsetup-rs/tests/tests.rs new file mode 100644 index 0000000..57ce4b7 --- /dev/null +++ b/patch/cryptsetup-rs/tests/tests.rs @@ -0,0 +1,72 @@ +#![deny(warnings)] + +extern crate cryptsetup_rs; +extern crate env_logger; +extern crate log; +extern crate tempdir; +extern crate uuid; + +#[macro_use] +extern crate expectest; + +use std::process::Command; + +use expectest::prelude::*; +use tempdir::TempDir; +use uuid::Uuid; + +use cryptsetup_rs::*; + +struct TestContext { + dir: TempDir, + name: String, +} + +impl TestContext { + fn new(name: String) -> TestContext { + env_logger::init(); + cryptsetup_rs::enable_debug(true); + let dir = tempdir::TempDir::new(&name).unwrap(); + TestContext { name, dir } + } + + fn new_crypt_device(&self) -> api::CryptDeviceFormatBuilder { + let crypt_file = self.dir.path().join(format!("{}.image", self.name)); + let dd_status = Command::new("dd") + .arg("if=/dev/zero") + .arg(format!("of={}", crypt_file.display())) + .arg("bs=1M") + .arg("count=10") + .status() + .unwrap(); + if !dd_status.success() { + panic!("Failed to create disk image at {}", crypt_file.display()); + } + + cryptsetup_rs::format(crypt_file).unwrap() + } +} + +#[test] +fn test_create_new_luks1_cryptdevice_no_errors() { + let ctx = TestContext::new("new_luks1_cryptdevice".to_string()); + let uuid = Uuid::new_v4(); + + let device_format = ctx.new_crypt_device() + .rng_type(crypt_rng_type::CRYPT_RNG_URANDOM) + .iteration_time(42); + + let mut dev = device_format + .luks1("aes", "xts-plain", "sha256", 256, Some(&uuid)) + .expect("LUKS format should succeed"); + + dev.dump(); + + expect!(dev.uuid()).to(be_equal_to(uuid)); + expect!(dev.device_type()).to(be_equal_to(crypt_device_type::LUKS1)); + expect!(dev.cipher()).to(be_equal_to("aes")); + expect!(dev.cipher_mode()).to(be_equal_to("xts-plain")); + expect!(dev.volume_key_size()).to(be_equal_to(32)); + + expect!(dev.add_keyslot(b"hello world", None, Some(3))).to(be_ok().value(3)); +} diff --git a/patch/ctap/.gitignore b/patch/ctap/.gitignore new file mode 100644 index 0000000..940e2fd --- /dev/null +++ b/patch/ctap/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +Cargo.lock +src/bin diff --git a/patch/ctap/Cargo.toml b/patch/ctap/Cargo.toml new file mode 100644 index 0000000..94f2be1 --- /dev/null +++ b/patch/ctap/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "ctap" +description = "A Rust implementation of the FIDO2 CTAP protocol" +version = "0.1.0" +license = "Apache-2.0/MIT" +homepage = "https://github.com/ArdaXi/ctap" +repository = "https://github.com/ArdaXi/ctap" +authors = ["Arda Xi "] +edition = "2018" + +[dependencies] +rand = "0.6" +failure = "0.1" +failure_derive = "0.1" +num-traits = "0.2" +num-derive = "0.2" +byteorder = "1" +cbor-codec = "0.7" +ring = "0.13" +untrusted = "0.6" +rust-crypto = "0.2" diff --git a/patch/ctap/README.md b/patch/ctap/README.md new file mode 100644 index 0000000..f44efd4 --- /dev/null +++ b/patch/ctap/README.md @@ -0,0 +1,59 @@ +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. + +# ctap + +ctap is a library implementing the [FIDO2 CTAP](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html) protocol. + +## Usage example + +```rust +let devices = ctap::get_devices()?; +let device_info = &devices[0]; +let mut device = ctap::FidoDevice::new(device_info)?; + +// This can be omitted if the FIDO device is not configured with a PIN. +let pin = "test"; +device.unlock(pin)?; + +// In a real application these values would come from the requesting app. +let rp_id = "rp_id"; +let user_id = [0]; +let user_name = "user_name"; +let client_data_hash = [0; 32]; +let cred = device.make_credential( + rp_id, + &user_id, + user_name, + &client_data_hash +)?; + +// In a real application the credential would be stored and used later. +let result = device.get_assertion(&cred, &client_data_hash); +``` + +## Limitations + +Currently, this library only supports Linux. Testing and contributions for +other platforms is welcome. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/patch/ctap/src/LICENSE-APACHE b/patch/ctap/src/LICENSE-APACHE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/patch/ctap/src/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/patch/ctap/src/LICENSE-MIT b/patch/ctap/src/LICENSE-MIT new file mode 100644 index 0000000..f61ddce --- /dev/null +++ b/patch/ctap/src/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) Ariën Holthuizen + +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/patch/ctap/src/cbor.rs b/patch/ctap/src/cbor.rs new file mode 100644 index 0000000..38d966f --- /dev/null +++ b/patch/ctap/src/cbor.rs @@ -0,0 +1,715 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +use cbor_codec::value; +use cbor_codec::value::Value; +use cbor_codec::{Config, Decoder, Encoder, GenericDecoder, GenericEncoder}; + +use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; +use failure::ResultExt; + +use std::collections::HashMap; +use std::io::Cursor; + +use super::error::*; + +pub enum Request<'a> { + MakeCredential(MakeCredentialRequest<'a>), + GetAssertion(GetAssertionRequest<'a>), + GetInfo, + ClientPin(ClientPinRequest<'a>), +} + +impl<'a> Request<'a> { + pub fn encode(&self, writer: &mut W) -> FidoResult<()> { + let mut encoder = Encoder::new(writer); + match self { + Request::MakeCredential(req) => req.encode(&mut encoder), + Request::GetAssertion(req) => req.encode(&mut encoder), + Request::GetInfo => encoder + .writer() + .write_u8(0x04) + .context(FidoErrorKind::CborEncode) + .map_err(From::from), + Request::ClientPin(req) => req.encode(&mut encoder), + } + } + + pub fn decode(&self, reader: R) -> FidoResult { + Ok(match self { + Request::MakeCredential(_) => { + Response::MakeCredential(MakeCredentialResponse::decode(reader)?) + } + Request::GetAssertion(_) => { + Response::GetAssertion(GetAssertionResponse::decode(reader)?) + } + Request::GetInfo => Response::GetInfo(GetInfoResponse::decode(reader)?), + Request::ClientPin(_) => Response::ClientPin(ClientPinResponse::decode(reader)?), + }) + } +} + +#[derive(Debug)] +pub enum Response { + MakeCredential(MakeCredentialResponse), + GetAssertion(GetAssertionResponse), + GetInfo(GetInfoResponse), + ClientPin(ClientPinResponse), +} + +#[derive(Default, Debug)] +pub struct MakeCredentialRequest<'a> { + pub client_data_hash: &'a [u8], + pub rp: PublicKeyCredentialRpEntity<'a>, + pub user: PublicKeyCredentialUserEntity<'a>, + pub pub_key_cred_params: &'a [(&'a str, i32)], + pub exclude_list: &'a [PublicKeyCredentialDescriptor], + pub extensions: &'a [(&'a str, &'a Value)], + pub options: Option, + pub pin_auth: Option<[u8; 16]>, + pub pin_protocol: Option, +} + +impl<'a> MakeCredentialRequest<'a> { + pub fn encode(&self, mut encoder: &mut Encoder) -> FidoResult<()> { + encoder + .writer() + .write_u8(0x01) + .context(FidoErrorKind::CborEncode)?; // authenticatorMakeCredential + let mut length = 4; + length += !self.exclude_list.is_empty() as usize; + length += !self.extensions.is_empty() as usize; + length += self.options.is_some() as usize; + length += self.pin_auth.is_some() as usize; + length += self.pin_protocol.is_some() as usize; + encoder.object(length)?; + encoder.u8(0x01)?; // clientDataHash + encoder.bytes(&self.client_data_hash)?; + encoder.u8(0x02)?; // rp + self.rp.encode(&mut encoder)?; + encoder.u8(0x03)?; // user + self.user.encode(&mut encoder)?; + encoder.u8(0x04)?; // pubKeyCredParams + encoder.array(self.pub_key_cred_params.len())?; + for (cred_type, alg) in self.pub_key_cred_params { + encoder.object(2)?; + encoder.text("alg")?; + encoder.i32(*alg)?; + encoder.text("type")?; + encoder.text(&cred_type)?; + } + if self.exclude_list.len() > 0 { + encoder.u8(0x05)?; // excludeList + encoder.array(self.exclude_list.len())?; + for item in self.exclude_list { + item.encode(&mut encoder)?; + } + } + if self.extensions.len() > 0 { + encoder.u8(0x06)?; // extensions + encoder.object(self.extensions.len())?; + for (key, value) in self.extensions { + encoder.text(key)?; + let mut generic = GenericEncoder::new(encoder.writer()); + generic.value(value)?; + } + } + if let Some(options) = &self.options { + if options.encoded() { + encoder.u8(0x07)?; // options + options.encode(&mut encoder)?; + } + } + if let Some(pin_auth) = &self.pin_auth { + encoder.u8(0x08)?; // pinAuth + encoder.bytes(pin_auth)?; + } + if let Some(pin_protocol) = &self.pin_protocol { + encoder.u8(0x09)?; // pinProtocol + encoder.u8(*pin_protocol)?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct MakeCredentialResponse { + pub format: String, + pub auth_data: AuthenticatorData, +} + +impl MakeCredentialResponse { + pub fn decode(mut reader: R) -> FidoResult { + let status = reader.read_u8().context(FidoErrorKind::CborDecode)?; + if status != 0 { + Err(FidoErrorKind::CborError(status))? + } + let mut decoder = Decoder::new(Config::default(), reader); + let mut response = MakeCredentialResponse::default(); + for _ in 0..decoder.object()? { + let key = decoder.u8()?; + match key { + 0x01 => response.format = decoder.text()?, + 0x02 => response.auth_data = AuthenticatorData::from_bytes(&decoder.bytes()?)?, + 0x03 => break, // TODO: parse attestation + _ => continue, + } + } + Ok(response) + } +} + +#[derive(Debug, Default)] +pub struct GetAssertionRequest<'a> { + pub rp_id: &'a str, + pub client_data_hash: &'a [u8], + pub allow_list: &'a [PublicKeyCredentialDescriptor], + pub extensions: &'a [(&'a str, &'a Value)], + pub options: Option, + pub pin_auth: Option<[u8; 16]>, + pub pin_protocol: Option, +} + +impl<'a> GetAssertionRequest<'a> { + pub fn encode(&self, mut encoder: &mut Encoder) -> FidoResult<()> { + encoder + .writer() + .write_u8(0x02) + .context(FidoErrorKind::CborEncode)?; // authenticatorGetAssertion + let mut length = 2; + length += !self.allow_list.is_empty() as usize; + length += !self.extensions.is_empty() as usize; + length += self.options.is_some() as usize; + length += self.pin_auth.is_some() as usize; + length += self.pin_protocol.is_some() as usize; + encoder.object(length)?; + encoder.u8(0x01)?; // rpId + encoder.text(&self.rp_id)?; + encoder.u8(0x02)?; // clientDataHash + encoder.bytes(self.client_data_hash)?; + if !self.allow_list.is_empty() { + encoder.u8(0x03)?; // allowList + encoder.array(self.allow_list.len())?; + for item in self.allow_list { + item.encode(&mut encoder)?; + } + } + if self.extensions.len() > 0 { + encoder.u8(0x04)?; // extensions + encoder.object(self.extensions.len())?; + for (key, value) in self.extensions { + encoder.text(key)?; + let mut generic = GenericEncoder::new(encoder.writer()); + generic.value(value)?; + } + } + if let Some(options) = &self.options { + if options.encoded() { + encoder.u8(0x05)?; // options + options.encode(&mut encoder)?; + } + } + if let Some(pin_auth) = &self.pin_auth { + encoder.u8(0x06)?; // pinAuth + encoder.bytes(pin_auth)?; + } + if let Some(pin_protocol) = &self.pin_protocol { + encoder.u8(0x07)?; // pinProtocol + encoder.u8(*pin_protocol)?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct GetAssertionResponse { + pub credential: Option, + pub auth_data_bytes: Vec, + pub auth_data: AuthenticatorData, + pub signature: Vec, +} + +impl GetAssertionResponse { + pub fn decode(mut reader: R) -> FidoResult { + let status = reader.read_u8().context(FidoErrorKind::CborDecode)?; + if status != 0 { + Err(FidoErrorKind::CborError(status))? + } + let mut decoder = Decoder::new(Config::default(), reader); + let mut response = GetAssertionResponse::default(); + for _ in 0..decoder.object()? { + let key = decoder.u8()?; + match key { + 0x01 => { + response.credential = Some(PublicKeyCredentialDescriptor::decode(&mut decoder)?) + } + 0x02 => { + response.auth_data_bytes = decoder.bytes()?; + response.auth_data = AuthenticatorData::from_bytes(&response.auth_data_bytes)?; + } + 0x03 => response.signature = decoder.bytes()?, + _ => continue, + } + } + Ok(response) + } +} + +#[derive(Debug, Default)] +pub struct GetInfoResponse { + pub versions: Vec, + pub extensions: Vec, + pub aaguid: [u8; 16], + pub options: OptionsInfo, + pub max_msg_size: u16, + pub pin_protocols: Vec, +} + +impl GetInfoResponse { + pub fn decode(mut reader: R) -> FidoResult { + let status = reader.read_u8().context(FidoErrorKind::CborDecode)?; + if status != 0 { + Err(FidoErrorKind::CborError(status))? + } + let mut decoder = Decoder::new(Config::default(), reader); + let mut response = GetInfoResponse::default(); + for _ in 0..decoder.object()? { + match decoder.u8()? { + 0x01 => { + for _ in 0..decoder.array()? { + response.versions.push(decoder.text()?); + } + } + 0x02 => { + for _ in 0..decoder.array()? { + response.extensions.push(decoder.text()?); + } + } + 0x03 => response.aaguid.copy_from_slice(&decoder.bytes()?[..]), + 0x04 => response.options = OptionsInfo::decode(&mut decoder)?, + 0x05 => response.max_msg_size = decoder.u16()?, + 0x06 => { + for _ in 0..decoder.array()? { + response.pin_protocols.push(decoder.u8()?); + } + } + _ => continue, + } + } + Ok(response) + } +} + +#[derive(Debug, Default)] +pub struct ClientPinRequest<'a> { + pub pin_protocol: u8, + pub sub_command: u8, + pub key_agreement: Option<&'a CoseKey>, + pub pin_auth: Option<[u8; 16]>, + pub new_pin_enc: Option>, + pub pin_hash_enc: Option<[u8; 16]>, +} + +impl<'a> ClientPinRequest<'a> { + pub fn encode(&self, encoder: &mut Encoder) -> FidoResult<()> { + encoder + .writer() + .write_u8(0x06) + .context(FidoErrorKind::CborEncode)?; // authenticatorClientPIN + let mut length = 2; + length += self.key_agreement.is_some() as usize; + length += self.pin_auth.is_some() as usize; + length += self.new_pin_enc.is_some() as usize; + length += self.pin_hash_enc.is_some() as usize; + encoder.object(length)?; + encoder.u8(0x01)?; // pinProtocol + encoder.u8(self.pin_protocol)?; + encoder.u8(0x02)?; // subCommand + encoder.u8(self.sub_command)?; + if let Some(key_agreement) = self.key_agreement { + encoder.u8(0x03)?; // keyAgreement + key_agreement.encode(encoder)?; + } + if let Some(pin_auth) = &self.pin_auth { + encoder.u8(0x04)?; // pinAuth + encoder.bytes(pin_auth)?; + } + if let Some(new_pin_enc) = &self.new_pin_enc { + encoder.u8(0x05)?; // newPinEnc + encoder.bytes(&new_pin_enc)?; + } + if let Some(pin_hash_enc) = &self.pin_hash_enc { + encoder.u8(0x06)?; // pinHashEnc + encoder.bytes(pin_hash_enc)?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct ClientPinResponse { + pub key_agreement: Option, + pub pin_token: Option<[u8; 16]>, + pub retries: Option, +} + +impl ClientPinResponse { + pub fn decode(mut reader: R) -> FidoResult { + let status = reader.read_u8().context(FidoErrorKind::CborDecode)?; + if status != 0 { + Err(FidoErrorKind::CborError(status))? + } + let mut decoder = Decoder::new(Config::default(), reader); + let mut response = ClientPinResponse::default(); + for _ in 0..decoder.object()? { + match decoder.u8()? { + 0x01 => { + let mut generic = GenericDecoder::from_decoder(decoder); + response.key_agreement = Some(CoseKey::decode(&mut generic)?); + decoder = generic.into_inner(); + } + 0x02 => { + let mut pin_token = [0; 16]; + pin_token.copy_from_slice(&decoder.bytes()?[..]); + response.pin_token = Some(pin_token) + } + 0x03 => response.retries = Some(decoder.u8()?), + _ => continue, + } + } + Ok(response) + } +} + +#[derive(Debug)] +pub struct OptionsInfo { + pub plat: bool, + pub rk: bool, + pub client_pin: Option, + pub up: bool, + pub uv: Option, +} + +impl Default for OptionsInfo { + fn default() -> Self { + OptionsInfo { + plat: false, + rk: false, + client_pin: None, + up: true, + uv: None, + } + } +} + +impl OptionsInfo { + pub fn decode(decoder: &mut Decoder) -> FidoResult { + let mut options = OptionsInfo::default(); + for _ in 0..decoder.object()? { + match decoder.text()?.as_ref() { + "plat" => options.plat = decoder.bool()?, + "rk" => options.rk = decoder.bool()?, + "clientPin" => options.client_pin = Some(decoder.bool()?), + "up" => options.up = decoder.bool()?, + "uv" => options.uv = Some(decoder.bool()?), + _ => continue, + } + } + Ok(options) + } +} + +#[derive(Debug, Default)] +pub struct AuthenticatorData { + pub rp_id_hash: [u8; 32], + pub up: bool, + pub uv: bool, + pub sign_count: u32, + pub attested_credential_data: AttestedCredentialData, + pub extensions: HashMap, +} + +impl AuthenticatorData { + pub fn from_bytes(bytes: &[u8]) -> FidoResult { + let mut data = AuthenticatorData::default(); + data.rp_id_hash.copy_from_slice(&bytes[0..32]); + let flags = bytes[32]; + data.up = (flags & 0x01) == 0x01; + data.uv = (flags & 0x02) == 0x02; + let is_attested = (flags & 0x40) == 0x40; + let has_extension_data = (flags & 0x80) == 0x80; + data.sign_count = BigEndian::read_u32(&bytes[33..37]); + if bytes.len() < 38 { + return Ok(data); + } + + let mut cur = Cursor::new(&bytes[37..]); + if is_attested { + let attested_credential_data = AttestedCredentialData::from_bytes(&mut cur)?; + data.attested_credential_data = attested_credential_data; + if cur.position() >= (bytes.len() - 37) as u64 { + return Ok(data); + } + } + if has_extension_data { + let mut decoder = GenericDecoder::new(Config::default(), cur); + for _ in 0..decoder.borrow_mut().object()? { + let key = decoder.borrow_mut().text()?; + let value = decoder.value()?; + data.extensions.insert(key.to_string(), value); + } + } + Ok(data) + } +} + +#[derive(Debug, Default)] +pub struct AttestedCredentialData { + pub aaguid: [u8; 16], + pub credential_id: Vec, + pub credential_public_key: CoseKey, +} + +impl AttestedCredentialData { + pub fn from_bytes(cur: &mut Cursor<&[u8]>) -> FidoResult { + let mut response = AttestedCredentialData::default(); + let bytes = cur.get_ref(); + if bytes.is_empty() { + return Ok(response); + } + response.aaguid.copy_from_slice(&bytes[0..16]); + let id_length = BigEndian::read_u16(&bytes[16..18]) as usize; + response.credential_id = Vec::from(&bytes[18..(18 + id_length)]); + cur.set_position(18 + id_length as u64); + let mut decoder = GenericDecoder::new(Config::default(), cur); + response.credential_public_key = CoseKey::decode(&mut decoder)?; + Ok(response) + } +} + +#[derive(Debug, Default)] +pub struct P256Key { + x: [u8; 32], + y: [u8; 32], +} + +impl P256Key { + pub fn from_cose(cose: &CoseKey) -> FidoResult { + if cose.key_type != 2 || cose.algorithm != -7 { + Err(FidoErrorKind::KeyType)? + } + if let ( + Some(Value::U8(curve)), + Some(Value::Bytes(value::Bytes::Bytes(x))), + Some(Value::Bytes(value::Bytes::Bytes(y))), + ) = ( + cose.parameters.get(&-1), + cose.parameters.get(&-2), + cose.parameters.get(&-3), + ) { + if *curve != 1 { + Err(FidoErrorKind::KeyType)? + } + let mut key = P256Key::default(); + key.x.copy_from_slice(&x); + key.y.copy_from_slice(&y); + return Ok(key); + } + Err(FidoErrorKind::KeyType)? + } + + pub fn from_bytes(bytes: &[u8]) -> FidoResult { + if bytes.len() != 65 || bytes[0] != 0x04 { + Err(FidoErrorKind::CborDecode)? + } + let mut res = P256Key::default(); + res.x.copy_from_slice(&bytes[1..33]); + res.y.copy_from_slice(&bytes[33..65]); + Ok(res) + } + + pub fn to_cose(&self) -> CoseKey { + CoseKey { + key_type: 2, + algorithm: -7, + parameters: [ + (-1, Value::U8(1)), + (-2, Value::Bytes(value::Bytes::Bytes(self.x.to_vec()))), + (-3, Value::Bytes(value::Bytes::Bytes(self.y.to_vec()))), + ] + .iter() + .cloned() + .collect(), + } + } + + pub fn bytes(&self) -> [u8; 65] { + let mut bytes = [0; 65]; + bytes[0] = 0x04; + bytes[1..33].copy_from_slice(&self.x); + bytes[33..65].copy_from_slice(&self.y); + bytes + } +} + +#[derive(Debug, Default)] +pub struct CoseKey { + key_type: u16, + algorithm: i32, + parameters: HashMap, +} + +impl CoseKey { + pub fn encode(&self, encoder: &mut Encoder) -> FidoResult<()> { + let size = 1 + self.parameters.len(); + encoder.object(size)?; + encoder.i16(0x01)?; // keyType + encoder.u16(self.key_type)?; + //encoder.i16(0x02)?; // algorithm + //encoder.i32(self.algorithm)?; + for (key, value) in self.parameters.iter() { + encoder.i16(*key)?; + let mut generic = GenericEncoder::new(encoder.writer()); + generic.value(value)?; + } + Ok(()) + } + + pub fn decode(generic: &mut GenericDecoder) -> FidoResult { + let items; + { + let decoder = generic.borrow_mut(); + items = decoder.object()?; + } + let mut cose_key = CoseKey::default(); + cose_key.algorithm = -7; + for _ in 0..items { + match generic.borrow_mut().i16()? { + 0x01 => cose_key.key_type = generic.borrow_mut().u16()?, + 0x02 => cose_key.algorithm = generic.borrow_mut().i32()?, + key if key < 0 => { + cose_key.parameters.insert(key, generic.value()?); + } + _ => { + generic.value()?; // skip unknown parameter + } + } + } + Ok(cose_key) + } +} + +#[derive(Debug, Default)] +pub struct PublicKeyCredentialRpEntity<'a> { + pub id: &'a str, + pub name: Option<&'a str>, + pub icon: Option<&'a str>, +} + +impl<'a> PublicKeyCredentialRpEntity<'a> { + pub fn encode(&self, encoder: &mut Encoder) -> FidoResult<()> { + let mut length = 1; + length += self.name.is_some() as usize; + length += self.icon.is_some() as usize; + encoder.object(length)?; + encoder.text("id")?; + encoder.text(&self.id)?; + if let Some(icon) = &self.icon { + encoder.text("icon")?; + encoder.text(&icon)?; + } + if let Some(name) = &self.name { + encoder.text("name")?; + encoder.text(&name)?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct PublicKeyCredentialUserEntity<'a> { + pub id: &'a [u8], + pub name: &'a str, + pub icon: Option<&'a str>, + pub display_name: Option<&'a str>, +} + +impl<'a> PublicKeyCredentialUserEntity<'a> { + pub fn encode(&self, encoder: &mut Encoder) -> FidoResult<()> { + let mut length = 2; + length += self.icon.is_some() as usize; + length += self.display_name.is_some() as usize; + encoder.object(length)?; + encoder.text("id")?; + encoder.bytes(&self.id)?; + if let Some(icon) = &self.icon { + encoder.text("icon")?; + encoder.text(&icon)?; + } + encoder.text("name")?; + encoder.text(&self.name)?; + if let Some(display_name) = &self.display_name { + encoder.text("displayName")?; + encoder.text(&display_name)?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct PublicKeyCredentialDescriptor { + pub cred_type: String, + pub id: Vec, +} + +impl PublicKeyCredentialDescriptor { + pub fn decode(decoder: &mut Decoder) -> FidoResult { + let mut response = PublicKeyCredentialDescriptor::default(); + for _ in 0..decoder.object()? { + match decoder.text()?.as_ref() { + "id" => response.id = decoder.bytes()?, + "type" => response.cred_type = decoder.text()?, + _ => continue, + } + } + Ok(response) + } + + pub fn encode(&self, encoder: &mut Encoder) -> FidoResult<()> { + encoder.object(2)?; + encoder.text("id")?; + encoder.bytes(&self.id)?; + encoder.text("type")?; + encoder.text(&self.cred_type)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct AuthenticatorOptions { + pub rk: bool, + pub uv: bool, +} + +impl AuthenticatorOptions { + pub fn encoded(&self) -> bool { + self.rk || self.uv + } + + pub fn encode(&self, encoder: &mut Encoder) -> FidoResult<()> { + let length = (self.rk as usize) + (self.uv as usize); + encoder.object(length)?; + if self.rk { + encoder.text("rk")?; + encoder.bool(true)?; + } + if self.uv { + encoder.text("uv")?; + encoder.bool(true)?; + } + Ok(()) + } +} diff --git a/patch/ctap/src/crypto.rs b/patch/ctap/src/crypto.rs new file mode 100644 index 0000000..1fa4902 --- /dev/null +++ b/patch/ctap/src/crypto.rs @@ -0,0 +1,130 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +use super::cbor::{CoseKey, P256Key}; +use super::error::*; +use failure::ResultExt; +use ring::error::Unspecified; +use ring::{agreement, digest, hmac, rand, signature}; +use rust_crypto::aes; +use rust_crypto::blockmodes::NoPadding; +use rust_crypto::buffer::{RefReadBuffer, RefWriteBuffer}; +use rust_crypto::symmetriccipher::{Decryptor, Encryptor}; +use untrusted::Input; + +#[derive(Debug)] +pub struct SharedSecret { + pub public_key: CoseKey, + pub shared_secret: [u8; 32], +} + +impl SharedSecret { + pub fn new(peer_key: &CoseKey) -> FidoResult { + let rng = rand::SystemRandom::new(); + let private = agreement::EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng) + .context(FidoErrorKind::GenerateKey)?; + let public = &mut [0u8; agreement::PUBLIC_KEY_MAX_LEN][..private.public_key_len()]; + private + .compute_public_key(public) + .context(FidoErrorKind::GenerateKey)?; + let peer = P256Key::from_cose(peer_key) + .context(FidoErrorKind::ParsePublic)? + .bytes(); + let peer = Input::from(&peer); + let shared_secret = agreement::agree_ephemeral( + private, + &agreement::ECDH_P256, + peer, + Unspecified, + |material| Ok(digest::digest(&digest::SHA256, material)), + ) + .context(FidoErrorKind::GenerateSecret)?; + let mut res = SharedSecret { + public_key: P256Key::from_bytes(&public) + .context(FidoErrorKind::ParsePublic)? + .to_cose(), + shared_secret: [0; 32], + }; + res.shared_secret.copy_from_slice(shared_secret.as_ref()); + Ok(res) + } + + pub fn encryptor(&self) -> Box { + aes::cbc_encryptor( + aes::KeySize::KeySize256, + &self.shared_secret, + &[0u8; 16], + NoPadding, + ) + } + + pub fn encrypt_pin(&self, pin: &str) -> FidoResult<[u8; 16]> { + let mut encryptor = self.encryptor(); + let pin_bytes = pin.as_bytes(); + let hash = digest::digest(&digest::SHA256, &pin_bytes); + let in_bytes = &hash.as_ref()[0..16]; + let mut input = RefReadBuffer::new(&in_bytes); + let mut out_bytes = [0; 16]; + let mut output = RefWriteBuffer::new(&mut out_bytes); + encryptor + .encrypt(&mut input, &mut output, true) + .map_err(|_| FidoErrorKind::EncryptPin)?; + Ok(out_bytes) + } + + pub fn decryptor(&self) -> Box { + aes::cbc_decryptor( + aes::KeySize::KeySize256, + &self.shared_secret, + &[0u8; 16], + NoPadding, + ) + } + + pub fn decrypt_token(&self, data: &mut [u8]) -> FidoResult { + let mut decryptor = self.decryptor(); + let mut input = RefReadBuffer::new(data); + let mut out_bytes = [0; 16]; + let mut output = RefWriteBuffer::new(&mut out_bytes); + decryptor + .decrypt(&mut input, &mut output, true) + .map_err(|_| FidoErrorKind::DecryptPin)?; + Ok(PinToken(hmac::SigningKey::new(&digest::SHA256, &out_bytes))) + } +} + +pub struct PinToken(hmac::SigningKey); + +impl PinToken { + pub fn auth(&self, data: &[u8]) -> [u8; 16] { + let signature = hmac::sign(&self.0, &data); + let mut out = [0; 16]; + out.copy_from_slice(&signature.as_ref()[0..16]); + out + } +} + +pub fn verify_signature( + public_key: &[u8], + client_data: &[u8], + auth_data: &[u8], + signature: &[u8], +) -> bool { + let public_key = Input::from(&public_key); + let msg_len = client_data.len() + auth_data.len(); + let mut msg = Vec::with_capacity(msg_len); + msg.extend_from_slice(auth_data); + msg.extend_from_slice(client_data); + let msg = Input::from(&msg); + let signature = Input::from(signature); + signature::verify( + &signature::ECDSA_P256_SHA256_ASN1, + public_key, + msg, + signature, + ) + .is_ok() +} diff --git a/patch/ctap/src/error.rs b/patch/ctap/src/error.rs new file mode 100644 index 0000000..c1f5c74 --- /dev/null +++ b/patch/ctap/src/error.rs @@ -0,0 +1,101 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +use cbor_codec::{DecodeError, EncodeError}; + +use failure::{Backtrace, Context, Fail}; +use std::fmt; +use std::fmt::Display; + +pub type FidoResult = Result; + +#[derive(Debug)] +pub struct FidoError(Context); + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] +pub enum FidoErrorKind { + #[fail(display = "Read/write error with device.")] + Io, + #[fail(display = "Error while reading packet from device.")] + ReadPacket, + #[fail(display = "Error while writing packet to device.")] + WritePacket, + #[fail(display = "Error while parsing CTAP from device.")] + ParseCtap, + #[fail(display = "Error while encoding CBOR for device.")] + CborEncode, + #[fail(display = "Error while decoding CBOR from device.")] + CborDecode, + #[fail(display = "Packets received from device in the wrong order.")] + InvalidSequence, + #[fail(display = "Failed to generate private keypair.")] + GenerateKey, + #[fail(display = "Failed to generate shared secret.")] + GenerateSecret, + #[fail(display = "Failed to parse public key.")] + ParsePublic, + #[fail(display = "Failed to encrypt PIN.")] + EncryptPin, + #[fail(display = "Failed to decrypt PIN.")] + DecryptPin, + #[fail(display = "Supplied key has incorrect type.")] + KeyType, + #[fail(display = "Device returned error: 0x{:x}", _0)] + CborError(u8), + #[fail(display = "Device does not support FIDO2")] + DeviceUnsupported, + #[fail(display = "This operating requires a PIN but none was provided.")] + PinRequired, +} + +impl Fail for FidoError { + fn cause(&self) -> Option<&dyn Fail> { + self.0.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.0.backtrace() + } +} + +impl Display for FidoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl FidoError { + pub fn kind(&self) -> FidoErrorKind { + *self.0.get_context() + } +} + +impl From for FidoError { + #[inline(always)] + fn from(kind: FidoErrorKind) -> FidoError { + FidoError(Context::new(kind)) + } +} + +impl From> for FidoError { + fn from(inner: Context) -> FidoError { + FidoError(inner) + } +} + +impl From for FidoError { + #[inline(always)] + fn from(err: EncodeError) -> FidoError { + FidoError(err.context(FidoErrorKind::CborEncode)) + } +} + +impl From for FidoError { + #[inline(always)] + fn from(err: DecodeError) -> FidoError { + FidoError(err.context(FidoErrorKind::CborDecode)) + } +} diff --git a/patch/ctap/src/extensions/hmac.rs b/patch/ctap/src/extensions/hmac.rs new file mode 100644 index 0000000..96abd2d --- /dev/null +++ b/patch/ctap/src/extensions/hmac.rs @@ -0,0 +1,200 @@ +use crate::cbor; +use crate::{FidoCredential, FidoDevice, FidoErrorKind, FidoResult}; +use cbor_codec::value::{Bytes, Int, Key, Text, Value}; +use cbor_codec::Encoder; +use cbor_codec::{Config, GenericDecoder}; +use rust_crypto::buffer::{RefReadBuffer, RefWriteBuffer}; +use rust_crypto::digest::Digest; +use rust_crypto::hmac::Hmac; +use rust_crypto::mac::Mac; +use rust_crypto::sha2::Sha256; +use std::collections::BTreeMap; +use std::io::Cursor; + +#[derive(Debug, Clone)] +pub struct FidoHmacCredential { + pub id: Vec, + pub rp_id: String, +} + +impl From for FidoHmacCredential { + fn from(cred: FidoCredential) -> Self { + FidoHmacCredential { + id: cred.id, + rp_id: cred.rp_id, + } + } +} + +pub trait HmacExtension { + fn extension_name() -> &'static str { + "hmac-secret" + } + + fn get_dict(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult { + let mut map = BTreeMap::new(); + map.insert( + Key::Text(Text::Text(Self::extension_name().to_owned())), + self.get_data(salt, salt2)?, + ); + Ok(Value::Map(map)) + } + + fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult; + + fn make_hmac_credential(&mut self) -> FidoResult; + + fn get_hmac_assertion( + &mut self, + credential: &FidoHmacCredential, + salt: &[u8; 32], + salt2: Option<&[u8; 32]>, + ) -> FidoResult<([u8; 32], Option<[u8; 32]>)>; + + fn hmac_challange( + &mut self, + credential: &FidoHmacCredential, + input: &[u8], + ) -> FidoResult<[u8; 32]> { + let mut salt = [0u8; 32]; + let mut digest = Sha256::new(); + digest.input(input); + digest.result(&mut salt); + self.get_hmac_assertion(credential, &salt, None) + .map(|secret| secret.0) + } +} + +impl HmacExtension for FidoDevice { + fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult { + let shared_secret = self.shared_secret.as_ref().unwrap(); + let mut encryptor = shared_secret.encryptor(); + let mut salt_enc = [0u8; 64]; + let mut output = RefWriteBuffer::new(&mut salt_enc); + let mut encrypt = || { + encryptor.encrypt(&mut RefReadBuffer::new(salt), &mut output, salt2.is_none())?; + if let Some(salt2) = salt2 { + encryptor + .encrypt(&mut RefReadBuffer::new(salt2), &mut output, true) + .map(|_| ()) + } else { + Ok(()) + } + }; + encrypt().map_err(|_| FidoErrorKind::Io)?; + + let key_agreement = || { + let mut cur = Cursor::new(Vec::new()); + let mut encoder = Encoder::new(&mut cur); + shared_secret.public_key.encode(&mut encoder).unwrap(); + cur.set_position(0); + let mut dec = GenericDecoder::new(Config::default(), cur); + dec.value() + }; + + let mut map = BTreeMap::new(); + map.insert( + Key::Int(Int::from_i64(0x01)), + key_agreement().map_err(|_| FidoErrorKind::Io)?, + ); + map.insert( + Key::Int(Int::from_i64(0x02)), + Value::Bytes(Bytes::Bytes( + salt_enc[0..((salt2.is_some() as usize + 1) * 32)].to_vec(), + )), + ); + + let mut salt_hmac = Hmac::new(Sha256::new(), &shared_secret.shared_secret); + salt_hmac.input(&salt_enc[0..((salt2.is_some() as usize + 1) * 32)]); + + let mut authed_salt_enc = [0u8; 32]; + authed_salt_enc.copy_from_slice(salt_hmac.result().code()); + + map.insert( + Key::Int(Int::from_i64(0x03)), + Value::Bytes(Bytes::Bytes(authed_salt_enc[0..16].to_vec())), + ); + + Ok(Value::Map(map)) + } + + fn make_hmac_credential(&mut self) -> FidoResult { + self.make_credential("hmac", &[0u8], "commandline", &[0u8; 32]) + .map(|cred| cred.into()) + } + + fn get_hmac_assertion( + &mut self, + credential: &FidoHmacCredential, + salt: &[u8; 32], + salt2: Option<&[u8; 32]>, + ) -> FidoResult<([u8; 32], Option<[u8; 32]>)> { + let client_data_hash = [0u8; 32]; + while self.shared_secret.is_none() { + self.init_shared_secret()?; + } + if self.needs_pin && self.pin_token.is_none() { + Err(FidoErrorKind::PinRequired)? + } + + if client_data_hash.len() != 32 { + Err(FidoErrorKind::CborEncode)? + } + let pin_auth = self + .pin_token + .as_ref() + .map(|token| token.auth(&client_data_hash)); + let ext_data: Value = self.get_data(salt, salt2)?; + let allow_list = [cbor::PublicKeyCredentialDescriptor { + cred_type: String::from("public-key"), + id: credential.id.clone(), + }]; + let request = cbor::GetAssertionRequest { + rp_id: &credential.rp_id, + client_data_hash: &client_data_hash, + allow_list: &allow_list, + extensions: &[(::extension_name(), &ext_data)], + options: Some(cbor::AuthenticatorOptions { + rk: false, + uv: true, + }), + pin_auth, + pin_protocol: pin_auth.and(Some(0x01)), + }; + let response = match self.cbor(cbor::Request::GetAssertion(request))? { + cbor::Response::GetAssertion(resp) => resp, + _ => Err(FidoErrorKind::CborDecode)?, + }; + let shared_secret = self.shared_secret.as_ref().unwrap(); + let mut decryptor = shared_secret.decryptor(); + let mut hmac_secret_combined = [0u8; 64]; + let _output = RefWriteBuffer::new(&mut hmac_secret_combined); + let hmac_secret_enc = match response + .auth_data + .extensions + .get(::extension_name()) + .ok_or(FidoErrorKind::CborDecode)? + { + Value::Bytes(hmac_ciphered) => Ok(match hmac_ciphered { + Bytes::Bytes(hmac_ciphered) => hmac_ciphered.to_vec(), + Bytes::Chunks(hmac_ciphered) => hmac_ciphered.iter().fold(Vec::new(), |s, i| { + let mut s = s; + s.extend_from_slice(&i); + s + }), + }), + _ => Err(FidoErrorKind::CborDecode), + }?; + let mut hmac_secret = ([0u8; 32], [0u8; 32]); + decryptor + .decrypt( + &mut RefReadBuffer::new(&hmac_secret_enc), + &mut RefWriteBuffer::new(unsafe { + std::mem::transmute::<_, &mut [u8; 64]>(&mut hmac_secret) + }), + true, + ) + .expect("failed to decrypt secret"); + Ok((hmac_secret.0, salt2.map(|_| hmac_secret.1))) + } +} diff --git a/patch/ctap/src/extensions/mod.rs b/patch/ctap/src/extensions/mod.rs new file mode 100644 index 0000000..c0f9333 --- /dev/null +++ b/patch/ctap/src/extensions/mod.rs @@ -0,0 +1 @@ +pub mod hmac; diff --git a/patch/ctap/src/hid_common.rs b/patch/ctap/src/hid_common.rs new file mode 100644 index 0000000..794a5fa --- /dev/null +++ b/patch/ctap/src/hid_common.rs @@ -0,0 +1,16 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +use std::path::PathBuf; + +#[derive(Debug, Clone)] +/// Storage for device related information +pub struct DeviceInfo { + pub path: PathBuf, + pub usage_page: u16, + pub usage: u16, + pub report_size: u16, +} diff --git a/patch/ctap/src/hid_linux.rs b/patch/ctap/src/hid_linux.rs new file mode 100644 index 0000000..ee819df --- /dev/null +++ b/patch/ctap/src/hid_linux.rs @@ -0,0 +1,88 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +pub use super::hid_common::*; +use byteorder::{ByteOrder, LittleEndian}; +use std::fs; +use std::io; +use std::path::PathBuf; + +static REPORT_DESCRIPTOR_KEY_MASK: u8 = 0xfc; +static LONG_ITEM_ENCODING: u8 = 0xfe; +static USAGE_PAGE: u8 = 0x04; +static USAGE: u8 = 0x08; +static REPORT_SIZE: u8 = 0x74; + +pub fn enumerate() -> io::Result> { + fs::read_dir("/sys/class/hidraw").map(|entries| { + entries + .filter_map(|entry| entry.ok()) + .filter_map(|entry| path_to_device(&entry.path()).ok()) + }) +} + +fn path_to_device(path: &PathBuf) -> io::Result { + let mut rd_path = path.clone(); + rd_path.push("device/report_descriptor"); + let rd = fs::read(rd_path)?; + let mut usage_page: u16 = 0; + let mut usage: u16 = 0; + let mut report_size: u16 = 0; + let mut pos: usize = 0; + + while pos < rd.len() { + let key = rd[pos]; + let mut key_size: usize = 1; + let mut size: u8; + + if key == LONG_ITEM_ENCODING { + key_size = 3; + size = rd[pos + 1]; + } else { + size = key & 0x03; + + if size == 0x03 { + size = 0x04 + } + } + + if key & REPORT_DESCRIPTOR_KEY_MASK == USAGE_PAGE { + if size != 2 { + usage_page = u16::from(rd[pos + 1]) + } else { + usage_page = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]); + } + } + + if key & REPORT_DESCRIPTOR_KEY_MASK == USAGE { + if size != 2 { + usage = u16::from(rd[pos + 1]) + } else { + usage = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]); + } + } + + if key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_SIZE { + if size != 2 { + report_size = u16::from(rd[pos + 1]) + } else { + report_size = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]); + } + } + + pos = pos + key_size + size as usize; + } + + let mut device_path = PathBuf::from("/dev"); + device_path.push(path.file_name().unwrap()); + + Ok(DeviceInfo { + path: device_path, + usage_page, + usage, + report_size, + }) +} diff --git a/patch/ctap/src/lib.rs b/patch/ctap/src/lib.rs new file mode 100644 index 0000000..1d7acd0 --- /dev/null +++ b/patch/ctap/src/lib.rs @@ -0,0 +1,402 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +//! An implementation of the CTAP2 protocol over USB. +//! +//! # Example +//! +//! ``` +//! # fn do_fido() -> ctap::FidoResult<()> { +//! let mut devices = ctap::get_devices()?; +//! let device_info = &devices.next().unwrap(); +//! let mut device = ctap::FidoDevice::new(device_info)?; +//! +//! // This can be omitted if the FIDO device is not configured with a PIN. +//! let pin = "test"; +//! device.unlock(pin)?; +//! +//! // In a real application these values would come from the requesting app. +//! let rp_id = "rp_id"; +//! let user_id = [0]; +//! let user_name = "user_name"; +//! let client_data_hash = [0; 32]; +//! let cred = device.make_credential( +//! rp_id, +//! &user_id, +//! user_name, +//! &client_data_hash +//! )?; +//! +//! // In a real application the credential would be stored and used later. +//! let result = device.get_assertion(&cred, &client_data_hash); +//! # Ok(()) +//! # } + +#![allow(dead_code)] + +extern crate failure; +extern crate rand; +#[macro_use] +extern crate failure_derive; +#[macro_use] +extern crate num_derive; +extern crate byteorder; +extern crate cbor as cbor_codec; +extern crate crypto as rust_crypto; +extern crate num_traits; +extern crate ring; +extern crate untrusted; + +mod cbor; +mod crypto; +mod error; +pub mod extensions; +mod hid_common; +mod hid_linux; +mod packet; + +use std::cmp; +use std::fs; +use std::io::{Cursor, Write}; +use std::u16; +use std::u8; + +pub use self::error::*; +use self::hid_linux as hid; +use self::packet::CtapCommand; +use failure::{Fail, ResultExt}; +use num_traits::FromPrimitive; +use rand::prelude::*; + +static BROADCAST_CID: [u8; 4] = [0xff, 0xff, 0xff, 0xff]; + +/// Looks for any connected HID devices and returns those that support FIDO. +pub fn get_devices() -> FidoResult> { + hid::enumerate() + .context(FidoErrorKind::Io) + .map(|devices| devices.filter(|dev| dev.usage_page == 0xf1d0 && dev.usage == 0x21)) + .map_err(From::from) +} + +/// A credential created by a FIDO2 authenticator. +#[derive(Debug)] +pub struct FidoCredential { + /// The ID provided by the authenticator. + pub id: Vec, + /// The public key provided by the authenticator, in uncompressed form. + pub public_key: Vec, + /// The Relying Party ID provided by the platform when this key was generated. + pub rp_id: String, +} + +/// An opened FIDO authenticator. +pub struct FidoDevice { + device: fs::File, + packet_size: u16, + channel_id: [u8; 4], + needs_pin: bool, + shared_secret: Option, + pin_token: Option, + aaguid: [u8; 16], +} + +impl FidoDevice { + /// Open and initialize a given device. DeviceInfo is provided by the `get_devices` + /// function. This method will allocate a channel for this application, verify that + /// it supports FIDO2, and checks if a PIN is set. + /// + /// This method will fail if the device can't be opened, if the device returns + /// malformed data or if the device is not supported. + pub fn new(device: &hid::DeviceInfo) -> error::FidoResult { + let mut options = fs::OpenOptions::new(); + options.read(true).write(true); + let mut dev = FidoDevice { + device: options.open(&device.path).context(FidoErrorKind::Io)?, + packet_size: 64, + channel_id: BROADCAST_CID, + needs_pin: false, + shared_secret: None, + pin_token: None, + aaguid: [0; 16], + }; + dev.init()?; + Ok(dev) + } + + fn init(&mut self) -> FidoResult<()> { + let mut nonce = [0u8; 8]; + thread_rng().fill_bytes(&mut nonce); + let response = self.exchange(CtapCommand::Init, &nonce)?; + if response.len() < 17 || response[0..8] != nonce { + Err(FidoErrorKind::ParseCtap)? + } + let flags = response[16]; + if flags & 0x04 == 0 { + Err(FidoErrorKind::DeviceUnsupported)? + } + self.channel_id.copy_from_slice(&response[8..12]); + let response = match self.cbor(cbor::Request::GetInfo)? { + cbor::Response::GetInfo(resp) => resp, + _ => Err(FidoErrorKind::CborDecode)?, + }; + if !response.versions.iter().any(|ver| ver == "FIDO_2_0") { + Err(FidoErrorKind::DeviceUnsupported)? + } + if !response.pin_protocols.iter().any(|ver| *ver == 1) { + Err(FidoErrorKind::DeviceUnsupported)? + } + self.needs_pin = response.options.client_pin == Some(true); + self.aaguid = response.aaguid; + Ok(()) + } + + /// Get the authenticator's AAGUID. This is not unique to an authenticator, + /// but it is unique to the specific brand and model. + pub fn aaguid(&self) -> &[u8] { + &self.aaguid + } + + fn init_shared_secret(&mut self) -> FidoResult<()> { + let mut request = cbor::ClientPinRequest::default(); + request.pin_protocol = 1; + request.sub_command = 0x02; // getKeyAgreement + let response = match self.cbor(cbor::Request::ClientPin(request))? { + cbor::Response::ClientPin(resp) => resp, + _ => Err(FidoErrorKind::CborDecode)?, + }; + if let Some(key_agreement) = response.key_agreement { + self.shared_secret = Some(crypto::SharedSecret::new(&key_agreement)?); + Ok(()) + } else { + Err(FidoErrorKind::CborDecode)? + } + } + + /// Unlock the device with the provided PIN. Internally this will generate + /// an ECDH keypair, send the encrypted PIN to the device and store the PIN + /// token that the device generates on every power cycle. The PIN itself is + /// not stored. + /// + /// This method will fail if the device returns malformed data or the PIN is + /// incorrect. + pub fn unlock(&mut self, pin: &str) -> FidoResult<()> { + while self.shared_secret.is_none() { + self.init_shared_secret()?; + } + // If the PIN is invalid the device should create a new agreementKey, + // so we only replace shared_secret on success. + let shared_secret = self.shared_secret.take().unwrap(); + let mut request = cbor::ClientPinRequest::default(); + request.pin_protocol = 1; + request.sub_command = 0x05; // getPINToken + request.key_agreement = Some(&shared_secret.public_key); + request.pin_hash_enc = Some(shared_secret.encrypt_pin(pin)?); + let response = match self.cbor(cbor::Request::ClientPin(request))? { + cbor::Response::ClientPin(resp) => resp, + _ => Err(FidoErrorKind::CborDecode)?, + }; + if let Some(mut pin_token) = response.pin_token { + self.pin_token = Some(shared_secret.decrypt_token(&mut pin_token)?); + self.shared_secret = Some(shared_secret); + Ok(()) + } else { + Err(FidoErrorKind::CborDecode)? + } + } + + /// Request a new credential from the authenticator. The `rp_id` should be + /// a stable string used to identify the party for whom the credential is + /// created, for convenience it will be returned with the credential. + /// `user_id` and `user_name` are not required when requesting attestations + /// but they MAY be displayed to the user and MAY be stored on the device + /// to be returned with an attestation if the device supports this. + /// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`, + /// this is only used to verify the attestation provided by the + /// authenticator. When not implementing WebAuthN this can be any random + /// 32-byte array. + /// + /// This method will fail if a PIN is required but the device is not + /// unlocked or if the device returns malformed data. + pub fn make_credential( + &mut self, + rp_id: &str, + user_id: &[u8], + user_name: &str, + client_data_hash: &[u8], + ) -> FidoResult { + if self.needs_pin && self.pin_token.is_none() { + Err(FidoErrorKind::PinRequired)? + } + if client_data_hash.len() != 32 { + Err(FidoErrorKind::CborEncode)? + } + let pin_auth = self + .pin_token + .as_ref() + .map(|token| token.auth(&client_data_hash)); + let rp = cbor::PublicKeyCredentialRpEntity { + id: rp_id, + name: None, + icon: None, + }; + let user = cbor::PublicKeyCredentialUserEntity { + id: user_id, + name: user_name, + icon: None, + display_name: None, + }; + let pub_key_cred_params = [("public-key", -7)]; + let request = cbor::MakeCredentialRequest { + client_data_hash, + rp, + user, + pub_key_cred_params: &pub_key_cred_params, + exclude_list: Default::default(), + extensions: Default::default(), + options: Some(cbor::AuthenticatorOptions { + rk: false, + uv: true, + }), + pin_auth, + pin_protocol: pin_auth.and(Some(0x01)), + }; + let response = match self.cbor(cbor::Request::MakeCredential(request))? { + cbor::Response::MakeCredential(resp) => resp, + _ => Err(FidoErrorKind::CborDecode)?, + }; + let public_key = cbor::P256Key::from_cose( + &response + .auth_data + .attested_credential_data + .credential_public_key, + )? + .bytes(); + Ok(FidoCredential { + id: response.auth_data.attested_credential_data.credential_id, + rp_id: String::from(rp_id), + public_key: Vec::from(&public_key[..]), + }) + } + + /// Request an assertion from the authenticator for a given credential. + /// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`, + /// this is signed and verified as part of the attestation. When not + /// implementing WebAuthN this can be any random 32-byte array. + /// + /// This method will return whether the assertion matches the credential + /// provided, and will fail if a PIN is required but not provided or if the + /// device returns malformed data. + pub fn get_assertion( + &mut self, + credential: &FidoCredential, + client_data_hash: &[u8], + ) -> FidoResult { + if self.needs_pin && self.pin_token.is_none() { + Err(FidoErrorKind::PinRequired)? + } + if client_data_hash.len() != 32 { + Err(FidoErrorKind::CborEncode)? + } + let pin_auth = self + .pin_token + .as_ref() + .map(|token| token.auth(&client_data_hash)); + let allow_list = [cbor::PublicKeyCredentialDescriptor { + cred_type: String::from("public-key"), + id: credential.id.clone(), + }]; + let request = cbor::GetAssertionRequest { + rp_id: &credential.rp_id, + client_data_hash: client_data_hash, + allow_list: &allow_list, + extensions: Default::default(), + options: Some(cbor::AuthenticatorOptions { + rk: false, + uv: true, + }), + pin_auth, + pin_protocol: pin_auth.and(Some(0x01)), + }; + let response = match self.cbor(cbor::Request::GetAssertion(request))? { + cbor::Response::GetAssertion(resp) => resp, + _ => Err(FidoErrorKind::CborDecode)?, + }; + Ok(crypto::verify_signature( + &credential.public_key, + &client_data_hash, + &response.auth_data_bytes, + &response.signature, + )) + } + + fn cbor(&mut self, request: cbor::Request) -> FidoResult { + let mut buf = Cursor::new(Vec::new()); + request + .encode(&mut buf) + .context(FidoErrorKind::CborEncode)?; + let response = self.exchange(CtapCommand::Cbor, &buf.into_inner())?; + request + .decode(Cursor::new(response)) + .context(FidoErrorKind::CborDecode) + .map_err(From::from) + } + + fn exchange(&mut self, cmd: CtapCommand, payload: &[u8]) -> FidoResult> { + self.send(&cmd, payload)?; + self.receive(&cmd) + } + + fn send(&mut self, cmd: &CtapCommand, payload: &[u8]) -> FidoResult<()> { + if payload.is_empty() || payload.len() > u16::MAX as usize { + Err(FidoErrorKind::WritePacket)? + } + let to_send = payload.len() as u16; + let max_payload = (self.packet_size - 7) as usize; + let (frame, payload) = payload.split_at(cmp::min(payload.len(), max_payload)); + packet::write_init_packet(&mut self.device, 64, &self.channel_id, cmd, to_send, frame)?; + if payload.is_empty() { + return Ok(()); + } + let max_payload = (self.packet_size - 5) as usize; + for (seq, frame) in (0..u8::MAX).zip(payload.chunks(max_payload)) { + packet::write_cont_packet(&mut self.device, 64, &self.channel_id, seq, frame)?; + } + self.device.flush().context(FidoErrorKind::WritePacket)?; + Ok(()) + } + + fn receive(&mut self, cmd: &CtapCommand) -> FidoResult> { + let mut first_packet: Option = None; + while first_packet.is_none() { + let packet = packet::InitPacket::from_reader(&mut self.device, 64)?; + if packet.cmd == CtapCommand::Error { + Err(packet::CtapError::from_u8(packet.payload[0]) + .unwrap_or(packet::CtapError::Other) + .context(FidoErrorKind::ParseCtap))? + } + if packet.cid == self.channel_id && &packet.cmd == cmd { + first_packet = Some(packet); + } + } + let first_packet = first_packet.unwrap(); + let mut data = first_packet.payload; + let mut to_read = (first_packet.size as isize) - data.len() as isize; + let mut seq = 0; + while to_read > 0 { + let packet = packet::ContPacket::from_reader(&mut self.device, 64, to_read as usize)?; + if packet.cid != self.channel_id { + continue; + } + if packet.seq != seq { + Err(FidoErrorKind::InvalidSequence)? + } + to_read -= packet.payload.len() as isize; + data.extend(&packet.payload); + seq += 1; + } + Ok(data) + } +} diff --git a/patch/ctap/src/packet.rs b/patch/ctap/src/packet.rs new file mode 100644 index 0000000..e7108e8 --- /dev/null +++ b/patch/ctap/src/packet.rs @@ -0,0 +1,179 @@ +// This file is part of ctap, a Rust implementation of the FIDO2 protocol. +// Copyright (c) Ariën Holthuizen +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +use super::error::*; +use failure::ResultExt; +use num_traits::{FromPrimitive, ToPrimitive}; + +use std::io::{Read, Write}; + +static FRAME_INIT: u8 = 0x80; + +#[repr(u8)] +#[derive(FromPrimitive, ToPrimitive, PartialEq)] +pub enum CtapCommand { + Invalid = 0x00, + Ping = 0x01, + Msg = 0x03, + Lock = 0x04, + Init = 0x06, + Wink = 0x08, + Cbor = 0x10, + Cancel = 0x11, + Keepalive = 0x3b, + Error = 0x3f, +} + +impl CtapCommand { + pub fn to_wire_format(&self) -> u8 { + match self.to_u8() { + Some(x) => x, + None => 0x00, + } + } +} + +#[repr(u8)] +#[derive(FromPrimitive, Fail, Debug)] +pub enum CtapError { + #[fail(display = "The command in the request is invalid")] + InvalidCmd = 0x01, + #[fail(display = "The parameter(s) in the request is invalid")] + InvalidPar = 0x02, + #[fail(display = "The length field (BCNT) is invalid for the request ")] + InvalidLen = 0x03, + #[fail(display = "The sequence does not match expected value ")] + InvalidSeq = 0x04, + #[fail(display = "The message has timed out ")] + MsgTimeout = 0x05, + #[fail(display = "The device is busy for the requesting channel ")] + ChannelBusy = 0x06, + #[fail(display = "Command requires channel lock ")] + LockRequired = 0x0A, + #[fail(display = "Reserved error")] + NA = 0x0B, + #[fail(display = "Unspecified error")] + Other = 0x7F, +} + +pub fn write_init_packet( + mut writer: W, + report_size: usize, + cid: &[u8], + cmd: &CtapCommand, + size: u16, + payload: &[u8], +) -> FidoResult<()> { + if cid.len() != 4 { + Err(FidoErrorKind::WritePacket)? + } + let mut packet = Vec::with_capacity(report_size); + packet.push(0); + packet.extend_from_slice(cid); + packet.push(FRAME_INIT | cmd.to_wire_format()); + packet.push(((size >> 8) & 0xff) as u8); + packet.push((size & 0xff) as u8); + packet.extend_from_slice(payload); + if packet.len() > report_size + 1 { + Err(FidoErrorKind::WritePacket)? + } + packet.resize(report_size + 1, 0); + writer + .write_all(&packet) + .context(FidoErrorKind::WritePacket)?; + Ok(()) +} + +pub struct InitPacket { + pub cid: [u8; 4], + pub cmd: CtapCommand, + pub size: u16, + pub payload: Vec, +} + +impl InitPacket { + pub fn from_reader(mut reader: R, report_size: usize) -> FidoResult { + let mut buf = Vec::with_capacity(report_size); + buf.resize(report_size, 0); + reader + .read_exact(&mut buf[0..report_size]) + .context(FidoErrorKind::ReadPacket)?; + let mut cid = [0; 4]; + cid.copy_from_slice(&buf[0..4]); + let cmd = match CtapCommand::from_u8(buf[4] ^ FRAME_INIT) { + Some(cmd) => cmd, + None => CtapCommand::Invalid, + }; + let size = ((u16::from(buf[5])) << 8) | u16::from(buf[6]); + let payload_end = if (size as usize) >= (report_size - 7) { + report_size + } else { + size as usize + 7 + }; + let payload = buf.drain(7..payload_end).collect(); + Ok(InitPacket { + cid, + cmd, + size, + payload, + }) + } +} + +pub fn write_cont_packet( + mut writer: W, + report_size: usize, + cid: &[u8], + seq: u8, + payload: &[u8], +) -> FidoResult<()> { + if cid.len() != 4 { + Err(FidoErrorKind::WritePacket)? + } + let mut packet = Vec::with_capacity(report_size); + packet.push(0); + packet.extend_from_slice(cid); + packet.push(seq); + packet.extend_from_slice(payload); + if packet.len() > report_size + 1 { + Err(FidoErrorKind::WritePacket)? + } + packet.resize(report_size + 1, 0); + writer + .write_all(&packet) + .context(FidoErrorKind::WritePacket)?; + Ok(()) +} + +pub struct ContPacket { + pub cid: [u8; 4], + pub seq: u8, + pub payload: Vec, +} + +impl ContPacket { + pub fn from_reader( + mut reader: R, + report_size: usize, + expected_data: usize, + ) -> FidoResult { + let mut buf = Vec::with_capacity(report_size); + buf.resize(report_size, 0); + reader + .read_exact(&mut buf[0..report_size]) + .context(FidoErrorKind::ReadPacket)?; + let mut cid = [0; 4]; + cid.copy_from_slice(&buf[0..4]); + let seq = buf[4]; + let payload_end = if expected_data >= (report_size - 5) { + report_size + } else { + expected_data + 5 + }; + let payload = buf.drain(5..payload_end).collect(); + Ok(ContPacket { cid, seq, payload }) + } +}