diff --git a/.github/workflows/kotlin/action.yml b/.github/workflows/kotlin/action.yml index 37a7439ee..60e8f18cb 100644 --- a/.github/workflows/kotlin/action.yml +++ b/.github/workflows/kotlin/action.yml @@ -57,16 +57,16 @@ runs: echo " [target.aarch64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang\" [target.armv7-linux-androideabi] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang\" [target.i686-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang\" [target.x86_64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang\"" >> ./.cargo/config.toml - name: Build @@ -78,6 +78,16 @@ runs: export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle ln -sfn $ANDROID_SDK_ROOT/ndk/27.2.12479018 $ANDROID_NDK + # Set environment variables for blake3 custom build script + export CC_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang" + export AR_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang" + export AR_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang" + export AR_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang" + export AR_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + make release RELEASE=1 chmod +x gradlew diff --git a/.github/workflows/native/native-build-linux/action.yml b/.github/workflows/native/native-build-linux/action.yml index 299a64e1a..fe406674c 100644 --- a/.github/workflows/native/native-build-linux/action.yml +++ b/.github/workflows/native/native-build-linux/action.yml @@ -68,16 +68,16 @@ runs: echo " [target.aarch64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang\" [target.armv7-linux-androideabi] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang\" [target.i686-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang\" [target.x86_64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang\"" >> ./.cargo/config.toml @@ -89,7 +89,20 @@ runs: export ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk" export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle ln -sfn $ANDROID_SDK_ROOT/ndk/27.2.12479018 $ANDROID_NDK + + ls $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/ + cat ../../.cargo/config.toml + # Set environment variables for blake3 custom build script + export CC_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang" + export AR_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang" + export AR_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang" + export AR_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang" + export AR_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + cargo build -p devolutions-crypto-ffi --release --target=aarch64-linux-android cargo build -p devolutions-crypto-ffi --release --target=armv7-linux-androideabi cargo build -p devolutions-crypto-ffi --release --target=i686-linux-android diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cfc1c8e8..b15a99528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +## [0.9.2] - 2025-01-20 +- Online encryption feature + +## [0.9.1] - 2024-06-21 +- AAD feature + ## [0.9.0] - 2023-10-23 - Removes the `derive_keypair` function as it was not used and was broken. - Added `constant_time_equals` in the utils. diff --git a/Cargo.lock b/Cargo.lock index 29640f56e..278fa27e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" @@ -70,19 +70,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -104,6 +105,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "zeroize", +] [[package]] name = "askama" @@ -128,7 +132,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -216,6 +220,20 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "zeroize", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -248,9 +266,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" @@ -263,9 +281,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -281,7 +299,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -295,9 +313,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" dependencies = [ "shlex", ] @@ -345,9 +363,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", "clap_derive", @@ -355,9 +373,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", @@ -367,21 +385,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" @@ -389,16 +407,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -413,19 +421,13 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crypto-common" version = "0.1.6" @@ -461,7 +463,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -487,22 +489,25 @@ dependencies = [ [[package]] name = "devolutions-crypto" -version = "0.9.1" +version = "0.9.2" dependencies = [ "aead", "aes", "arbitrary", "base64 0.22.1", "blahaj", + "blake3", "byteorder", "cbc", "cfg-if", "chacha20poly1305", + "dyn-clone", "ed25519-dalek", "getrandom", "hmac", "js-sys", "num_enum", + "paste", "pbkdf2", "rand", "rand_core", @@ -512,7 +517,7 @@ dependencies = [ "sha2", "strum", "subtle", - "thiserror", + "thiserror 2.0.11", "typed-builder", "wasm-bindgen", "wasm-bindgen-test", @@ -522,7 +527,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-cli" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.11.0", "clap", @@ -531,7 +536,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-ffi" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.21.7", "devolutions-crypto", @@ -540,7 +545,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-fuzz" -version = "0.9.1" +version = "0.9.2" dependencies = [ "arbitrary", "devolutions-crypto", @@ -549,7 +554,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-python" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.21.7", "devolutions-crypto", @@ -559,7 +564,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-uniffi" -version = "0.9.1" +version = "0.9.2" dependencies = [ "devolutions-crypto", "uniffi", @@ -577,6 +582,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ed25519" version = "2.2.3" @@ -616,9 +627,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "fs-err" @@ -654,9 +665,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "goblin" @@ -671,9 +682,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -697,9 +708,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -729,24 +740,25 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.164" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" @@ -760,9 +772,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -839,7 +851,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -899,9 +911,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "ppv-lite86" @@ -923,9 +935,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -978,7 +990,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -991,7 +1003,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1005,9 +1017,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1044,14 +1056,13 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc" +checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" dependencies = [ "base64 0.21.7", "blake2b_simd", "constant_time_eq", - "crossbeam-utils", ] [[package]] @@ -1065,9 +1076,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -1093,12 +1104,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scroll" version = "0.12.0" @@ -1116,7 +1121,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1132,18 +1137,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -1161,20 +1166,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -1261,7 +1266,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1283,9 +1288,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1313,7 +1318,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -1324,7 +1338,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -1370,7 +1395,7 @@ checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1381,9 +1406,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" @@ -1419,7 +1444,7 @@ name = "uniffi-builder-macro" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1463,7 +1488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" dependencies = [ "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1493,7 +1518,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.89", + "syn 2.0.96", "toml", "uniffi_meta", ] @@ -1582,47 +1607,48 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1630,33 +1656,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -1664,20 +1691,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1776,9 +1803,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -1813,7 +1840,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1833,5 +1860,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] diff --git a/Cargo.toml b/Cargo.toml index 1067791b6..9e779e9aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -package.version = "0.9.1" +package.version = "0.9.2" members = [ "cli", "ffi", @@ -30,7 +30,7 @@ name = "devolutions_crypto" crate-type = ["cdylib", "rlib"] [dependencies] -aead = "0.5" +aead = { version = "0.5", features = ["stream"] } aes = "0.8" base64 = "0.22" cbc = { version = "0.1.2", features = ["block-padding", "alloc"] } @@ -48,8 +48,9 @@ subtle = "2" zeroize = { version = "1.8" } rand = "0.8" rand_core = { version = "0.6" } -thiserror = "1.0.64" +thiserror = "2.0.11" typed-builder = "0.20.0" +rust-argon2 = { version = "2.1", default-features = false } ed25519-dalek = { version = "2", features = [ "rand_core" ] } x25519-dalek = { version = "2", features = [ "static_secrets" ] } @@ -58,9 +59,11 @@ x25519-dalek = { version = "2", features = [ "static_secrets" ] } # Version is pinned because newer version requires lifetime annotations # that isn't compatible with the header trait arbitrary = { version = "0.4.7", features = ["derive"], optional = true } +blake3 = { version = "1.5.5", features = ["zeroize"] } +paste = "1.0.15" +dyn-clone = "1.0.17" [target.'cfg(target_arch="wasm32")'.dependencies] -rust-argon2 = { version = "1.0", default-features = false } wasm-bindgen = { version = "0.2.92", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } js-sys = { version = "0.3.69", optional = true } @@ -72,9 +75,6 @@ js-sys = { version = "0.3.69", optional = true } # all the features selected is used when building a Cargo project) getrandom = { version = "0.2", features = ["js", "wasm-bindgen"] } -[target.'cfg(not(target_arch="wasm32"))'.dependencies] -rust-argon2 = "1.0" - [target.'cfg(target_arch="wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/cli/src/main.rs b/cli/src/main.rs index 73361cd9a..47fe9128d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use std::convert::TryFrom; +use std::{borrow::Borrow, convert::TryFrom}; /// Gives a CLI interface to Devolutions Crypto Library #[derive(Debug, Parser)] @@ -243,7 +243,7 @@ fn generate_argon2parameters( parameters.length = length; }; - let parameters: Vec = parameters.into(); + let parameters: Vec = parameters.borrow().into(); println!("{}", base64::encode(¶meters)); } diff --git a/ffi/devolutions-crypto.h b/ffi/devolutions-crypto.h index 24bb294ee..f582791c9 100644 --- a/ffi/devolutions-crypto.h +++ b/ffi/devolutions-crypto.h @@ -64,6 +64,9 @@ int64_t DecodeUrl(const uint8_t *input, * * `data_length` - Length of the data to decrypt. * * `key` - Pointer to the key to use to decrypt. * * `key_length` - Length of the key to use to decrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the plaintext to. * * `result_length` - Length of the buffer to write the plaintext to. * The safest size is the same size as the ciphertext. @@ -77,6 +80,8 @@ int64_t Decrypt(const uint8_t *data, size_t data_length, const uint8_t *key, size_t key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length); @@ -87,6 +92,9 @@ int64_t Decrypt(const uint8_t *data, * * `data_length` - Length of the data to decrypt. * * `private_key` - Pointer to the private key to use to decrypt. * * `private_key_length` - Length of the private key to use to decrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the plaintext to. * * `result_length` - Length of the buffer to write the plaintext to. * The safest size is the same size as the ciphertext. @@ -100,6 +108,8 @@ int64_t DecryptAsymmetric(const uint8_t *data, size_t data_length, const uint8_t *private_key, size_t private_key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length); @@ -189,6 +199,9 @@ int64_t EncodeUrl(const uint8_t *input, * * `data_length` - Length of the data to encrypt. * * `key` - Pointer to the key to use to encrypt. * * `key_length` - Length of the key to use to encrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the ciphertext to. * * `result_length` - Length of the buffer to write the ciphertext to. You can get the value by * calling EncryptSize() beforehand. @@ -203,6 +216,8 @@ int64_t Encrypt(const uint8_t *data, size_t data_length, const uint8_t *key, size_t key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length, uint16_t version); @@ -214,6 +229,9 @@ int64_t Encrypt(const uint8_t *data, * * `data_length` - Length of the data to encrypt. * * `public_key` - Pointer to the public key to use to encrypt. * * `public_key_length` - Length of the public key to use to encrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the ciphertext to. * * `result_length` - Length of the buffer to write the ciphertext to. You can get the value by * calling EncryptAsymmetricSize() beforehand. @@ -228,6 +246,8 @@ int64_t EncryptAsymmetric(const uint8_t *data, size_t data_length, const uint8_t *public_key, size_t public_key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length, uint16_t version); @@ -251,6 +271,10 @@ int64_t EncryptAsymmetricSize(size_t data_length, */ int64_t EncryptSize(size_t data_length, uint16_t version); +void FreeOnlineDecryptor(void *ptr); + +void FreeOnlineEncryptor(void *ptr); + /** * Generate a key using a CSPRNG. * # Arguments @@ -373,6 +397,8 @@ int64_t GetDefaultArgon2ParametersSize(void); * * `public` - Pointer to the buffer to write the public key to. * * `public_length` - Length of the buffer to write the public key to. * You can get the value by calling `GetSigningPublicKeySize()` beforehand. + * # Safety + * This method is made to be called by C, so it is therefore unsafe. The caller should make sure it passes the right pointers and sizes. */ int64_t GetSigningPublicKey(const uint8_t *keypair, size_t keypair_length, @@ -482,6 +508,72 @@ int64_t MixKeyExchange(const uint8_t *private_, */ int64_t MixKeyExchangeSize(void); +int64_t NewOnlineDecryptor(const uint8_t *key, + size_t key_size, + const uint8_t *aad, + size_t aad_size, + const uint8_t *header, + size_t header_size, + bool asymmetric, + void **output); + +int64_t NewOnlineEncryptor(const uint8_t *key, + size_t key_size, + const uint8_t *aad, + size_t aad_size, + uint32_t chunk_size, + bool asymmetric, + uint16_t version, + void **output); + +int64_t OnlineDecryptorGetChunkSize(const void *ptr); + +int64_t OnlineDecryptorGetHeader(const void *ptr, uint8_t *result, size_t result_size); + +int64_t OnlineDecryptorGetHeaderSize(const void *ptr); + +int64_t OnlineDecryptorGetTagSize(const void *ptr); + +int64_t OnlineDecryptorLastChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + +int64_t OnlineDecryptorNextChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + +int64_t OnlineEncryptorGetChunkSize(const void *ptr); + +int64_t OnlineEncryptorGetHeader(const void *ptr, uint8_t *result, size_t result_size); + +int64_t OnlineEncryptorGetHeaderSize(const void *ptr); + +int64_t OnlineEncryptorGetTagSize(const void *ptr); + +int64_t OnlineEncryptorLastChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + +int64_t OnlineEncryptorNextChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + /** * This is binded here for one specific use case, do not use it if you don't know what you're doing. * # Safety diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 5157231b8..9a7f5ca3d 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -8,6 +8,9 @@ //! The Size functions must be called to get the required length of the returned array before //! calling it. +use devolutions_crypto::online_ciphertext::OnlineCiphertextDecryptor; +use devolutions_crypto::online_ciphertext::OnlineCiphertextEncryptor; +use devolutions_crypto::online_ciphertext::OnlineCiphertextHeader; use devolutions_crypto::utils; use devolutions_crypto::Argon2Parameters; use devolutions_crypto::DataType; @@ -25,6 +28,7 @@ use devolutions_crypto::password_hash::{hash_password, PasswordHash, PasswordHas use devolutions_crypto::secret_sharing::{ generate_shared_key, join_shares, SecretSharingVersion, Share, }; +use devolutions_crypto::OnlineCiphertextVersion; use devolutions_crypto::{ signature, signature::{Signature, SignatureVersion}, @@ -37,7 +41,11 @@ use devolutions_crypto::{ use devolutions_crypto::Result; +use std::borrow::Borrow; +use std::ffi::c_void; +use std::mem::MaybeUninit; use std::slice; +use std::sync::Mutex; use zeroize::Zeroizing; @@ -835,6 +843,397 @@ pub unsafe extern "C" fn JoinShares( } } +#[no_mangle] +pub unsafe extern "C" fn NewOnlineEncryptor( + key: *const u8, + key_size: usize, + aad: *const u8, + aad_size: usize, + chunk_size: u32, + asymmetric: bool, + version: u16, + output: *mut *mut c_void, +) -> i64 { + if key.is_null() || aad.is_null() { + return Error::NullPointer.error_code(); + }; + + let key = slice::from_raw_parts(key, key_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let version = match OnlineCiphertextVersion::try_from(version) { + Ok(v) => v, + Err(_) => return Error::UnknownVersion.error_code(), + }; + + let encryptor = if asymmetric { + let public_key = match PublicKey::try_from(key) { + Ok(pk) => pk, + Err(e) => return e.error_code(), + }; + + OnlineCiphertextEncryptor::new_asymmetric(&public_key, aad, chunk_size, version) + } else { + OnlineCiphertextEncryptor::new(key, aad, chunk_size, version) + }; + + let encryptor = Box::new(Mutex::new(encryptor)); + + *output = Box::into_raw(encryptor) as *mut c_void; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn NewOnlineDecryptor( + key: *const u8, + key_size: usize, + aad: *const u8, + aad_size: usize, + header: *const u8, + header_size: usize, + asymmetric: bool, + output: *mut *mut c_void, +) -> i64 { + if key.is_null() | aad.is_null() | header.is_null() { + return Error::NullPointer.error_code(); + }; + + let key = slice::from_raw_parts(key, key_size); + let aad = slice::from_raw_parts(aad, aad_size); + let header = slice::from_raw_parts(header, header_size); + + let header = match OnlineCiphertextHeader::try_from(header) { + Ok(h) => h, + Err(e) => return e.error_code(), + }; + + let decryptor = if asymmetric { + let private_key = match PrivateKey::try_from(key) { + Ok(pk) => pk, + Err(e) => return e.error_code(), + }; + + header.into_decryptor_asymmetric(&private_key, aad) + } else { + header.into_decryptor(key, aad) + }; + + let decryptor = match decryptor { + Ok(d) => d, + Err(e) => return e.error_code(), + }; + + let decryptor = Box::new(Mutex::new(decryptor)); + + *output = Box::into_raw(decryptor) as *mut c_void; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetHeader( + ptr: *const c_void, + result: *mut u8, + result_size: usize, +) -> i64 { + if ptr.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &*(ptr as *const Mutex); + let header: Vec = match encryptor.lock() { + Ok(c) => c.get_header().borrow().into(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + if header.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(header.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetHeader( + ptr: *const c_void, + result: *mut u8, + result_size: usize, +) -> i64 { + if ptr.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &*(ptr as *const Mutex); + let header: Vec = match decryptor.lock() { + Ok(c) => c.get_header().borrow().into(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + if header.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(header.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorNextChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &mut *(ptr as *mut Mutex); + let mut encryptor = match encryptor.lock() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let encrypted = match encryptor.encrypt_next_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if encrypted.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(encrypted.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorNextChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &mut *(ptr as *mut Mutex); + let mut decryptor = match decryptor.lock() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let decrypted = match decryptor.decrypt_next_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if decrypted.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(decrypted.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorLastChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = Box::from_raw(ptr as *mut Mutex); + + let encryptor = match encryptor.into_inner() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let encrypted = match encryptor.encrypt_last_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if result_size < encrypted.len() { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(encrypted.as_slice().as_ptr() as *const u8, encrypted.len()); + + encrypted.len() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorLastChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = Box::from_raw(ptr as *mut Mutex); + let decryptor = match decryptor.into_inner() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let decrypted = match decryptor.decrypt_last_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if result_size < decrypted.len() { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(decrypted.as_slice().as_ptr() as *const u8, decrypted.len()); + + decrypted.len() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetHeaderSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &*(ptr as *const Mutex); + let header = match encryptor.lock() { + Ok(c) => c.get_header(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + header.get_serialized_size() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetHeaderSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &*(ptr as *const Mutex); + let header = match decryptor.lock() { + Ok(c) => c.get_header(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + header.get_serialized_size() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetChunkSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &*(ptr as *const Mutex); + match encryptor.lock() { + Ok(c) => c.get_chunk_size() as i64, + Err(_) => Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetChunkSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &*(ptr as *const Mutex); + match decryptor.lock() { + Ok(c) => c.get_chunk_size() as i64, + Err(_) => Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetTagSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &*(ptr as *const Mutex); + match encryptor.lock() { + Ok(c) => c.get_tag_size() as i64, + Err(_) => return Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetTagSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &*(ptr as *const Mutex); + match decryptor.lock() { + Ok(c) => c.get_tag_size() as i64, + Err(_) => return Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn FreeOnlineEncryptor(ptr: *mut c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + drop(Box::from_raw(ptr as *mut Mutex)); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn FreeOnlineDecryptor(ptr: *mut c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + + drop(Box::from_raw(ptr as *mut Mutex)); + + 0 +} + /// The size, in bytes, of the resulting secret /// # Arguments /// * share_length - The length of a share diff --git a/fuzz/fuzz_targets/utils/derive_key.rs b/fuzz/fuzz_targets/utils/derive_key.rs index fb6d9415e..167344460 100644 --- a/fuzz/fuzz_targets/utils/derive_key.rs +++ b/fuzz/fuzz_targets/utils/derive_key.rs @@ -2,7 +2,7 @@ use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; -use devolutions_crypto::utils::derive_key; +use devolutions_crypto::utils::derive_key_pbkdf2; #[derive(Arbitrary, Clone, Debug)] struct Input { @@ -12,5 +12,5 @@ struct Input { } fuzz_target!(|data: Input| { - let _ = derive_key(&data.input, &data.salt, 10, data.length.into()); + let _ = derive_key_pbkdf2(&data.input, &data.salt, 10, data.length.into()); }); diff --git a/src/argon2parameters.rs b/src/argon2parameters.rs index 6a5a54143..636d0e3ea 100644 --- a/src/argon2parameters.rs +++ b/src/argon2parameters.rs @@ -3,7 +3,7 @@ use std::{ io::{Cursor, Read, Write}, }; -use argon2::{Config, ThreadMode, Variant, Version}; +use argon2::{Config, Variant, Version}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use rand_core::{OsRng, RngCore}; use typed_builder::TypedBuilder; @@ -35,6 +35,7 @@ pub mod defaults { } /// Parameters used to derive the password into an Argon2 hash. +/// /// It is used to derive a password into a keypair. /// You should use the default, although this may be tweakable by the user in some cases. /// Once serialized, you can save it along the user information as it is not sensitive data. @@ -206,12 +207,6 @@ impl Argon2Parameters { time_cost: self.iterations, variant: self.variant, version: self.version, - - #[cfg(target_arch = "wasm32")] - thread_mode: ThreadMode::Sequential, - - #[cfg(not(target_arch = "wasm32"))] - thread_mode: ThreadMode::Parallel, }; Ok(argon2::hash_raw(password, &self.salt, &config)?) diff --git a/src/ciphertext/ciphertext_v1.rs b/src/ciphertext/ciphertext_v1.rs index 0f4181563..8c619523f 100644 --- a/src/ciphertext/ciphertext_v1.rs +++ b/src/ciphertext/ciphertext_v1.rs @@ -91,7 +91,7 @@ impl CiphertextV1 { let ciphertext = cipher.encrypt_padded_vec_mut::(data); // Append MAC data - let mut mac_data: Vec = (*header).clone().into(); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); mac_data.extend_from_slice(&iv); mac_data.extend_from_slice(&ciphertext); @@ -116,7 +116,7 @@ impl CiphertextV1 { CiphertextV1::split_key(key, &mut encryption_key, &mut signature_key)?; // Verify HMAC - let mut mac_data: Zeroizing> = Zeroizing::new((*header).clone().into()); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); mac_data.extend_from_slice(&self.iv); mac_data.extend_from_slice(&self.ciphertext); diff --git a/src/ciphertext/ciphertext_v2.rs b/src/ciphertext/ciphertext_v2.rs index 2f067e279..a92fc1022 100644 --- a/src/ciphertext/ciphertext_v2.rs +++ b/src/ciphertext/ciphertext_v2.rs @@ -96,7 +96,7 @@ impl CiphertextV2Symmetric { let nonce = XNonce::from_slice(&nonce_bytes); // Authenticate the header - let mut mac_data: Vec = (*header).clone().into(); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); let payload = Payload { @@ -122,7 +122,7 @@ impl CiphertextV2Symmetric { let key = Zeroizing::new(CiphertextV2Symmetric::derive_key(key)); // Authenticate the header - let mut mac_data: Vec = (*header).clone().into(); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); let payload = Payload { diff --git a/src/ciphertext/mod.rs b/src/ciphertext/mod.rs index 5655a9027..06ef35f57 100644 --- a/src/ciphertext/mod.rs +++ b/src/ciphertext/mod.rs @@ -56,6 +56,7 @@ use super::key::{PrivateKey, PublicKey}; use ciphertext_v1::CiphertextV1; use ciphertext_v2::{CiphertextV2Asymmetric, CiphertextV2Symmetric}; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -324,7 +325,7 @@ impl Ciphertext { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: Ciphertext) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/enums.rs b/src/enums.rs index 202132ccd..11efcee0a 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -27,6 +27,8 @@ pub enum DataType { SigningKey = 5, /// A wrapped signature. Signature = 6, + /// A wrapped online ciphertextr that can be encrypted/decrypted chunk by chunk + OnlineCiphertext = 7, } impl Default for DataType { @@ -55,6 +57,24 @@ impl Default for CiphertextVersion { } } +/// The versions of the online encryption scheme to use. +#[cfg_attr(feature = "wbindgen", wasm_bindgen())] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] +#[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] +#[repr(u16)] +pub enum OnlineCiphertextVersion { + /// Uses the latest version. + Latest = 0, + /// Uses version 1: XChaCha20-Poly1305 wrapped in a STREAM construction. + V1 = 1, +} + +impl Default for OnlineCiphertextVersion { + fn default() -> Self { + Self::Latest + } +} + /// The versions of the password hashing scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] diff --git a/src/error.rs b/src/error.rs index aa8e34dcb..056eda6cf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,4 @@ //! Possible errors in the library. - use cbc::cipher::block_padding::UnpadError; #[cfg(feature = "wbindgen")] @@ -9,6 +8,19 @@ use strum::IntoStaticStr; use hmac::digest::MacError; +pub type Result = std::result::Result; + +// Doesn't work because Result is a type alias, keeping the commented code just in case we revisit someday +// impl From> for Result +// where E: Into { +// fn from(value: std::result::Result) -> Self { +// match value { +// Ok(t) => Ok(t), +// Err(e) => Err(e.into()), +// } +// } +// } + /// This crate's error type. #[derive(Debug, IntoStaticStr, thiserror::Error)] pub enum Error { @@ -60,6 +72,12 @@ pub enum Error { /// The version of the multiple data is inconsistent: -42 #[error("The version is not the same for all the data.")] InconsistentVersion, + /// The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size: -43 + #[error("The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size")] + InvalidChunkLength, + /// The mutex is poisoned and cannot be locked: -44 + #[error("The mutex is poisoned and cannot be locked")] + PoisonedMutex, } impl Error { @@ -83,6 +101,8 @@ impl Error { Error::IoError(_) => -34, Error::NotEnoughShares => -41, Error::InconsistentVersion => -42, + Error::InvalidChunkLength => -43, + Error::PoisonedMutex => -44, } } } diff --git a/src/header.rs b/src/header.rs index bf2406214..37140c119 100644 --- a/src/header.rs +++ b/src/header.rs @@ -104,18 +104,31 @@ where } } -impl From> for Vec +impl From<&Header> for Vec where M: HeaderType, { - fn from(header: Header) -> Vec { - let mut data = Vec::with_capacity(8); - data.write_u16::(header.signature).unwrap(); - data.write_u16::(header.data_type.into()) + fn from(header: &Header) -> Self { + <&Header as Into<[u8; 8]>>::into(header).to_vec() + } +} + +impl From<&Header> for [u8; 8] +where + M: HeaderType, +{ + fn from(header: &Header) -> Self { + let mut data = [0u8; 8]; + let mut cursor = Cursor::new(data.as_mut_slice()); + cursor.write_u16::(header.signature).unwrap(); + cursor + .write_u16::(header.data_type.into()) .unwrap(); - data.write_u16::(header.data_subtype.into()) + cursor + .write_u16::(header.data_subtype.clone().into()) .unwrap(); - data.write_u16::(header.version.into()) + cursor + .write_u16::(header.version.clone().into()) .unwrap(); data } diff --git a/src/key/mod.rs b/src/key/mod.rs index 1614be696..da5a7c0a3 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -52,6 +52,7 @@ use super::Result; use key_v1::{KeyV1Private, KeyV1Public}; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -231,7 +232,7 @@ fn keypair_headers(version: KeyVersion) -> (Header, Header for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: PublicKey) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header @@ -265,7 +266,7 @@ impl TryFrom<&[u8]> for PublicKey { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: PrivateKey) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/lib.rs b/src/lib.rs index cda22c4bb..e8569b4e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,6 +213,7 @@ mod header; pub mod ciphertext; pub mod key; +pub mod online_ciphertext; pub mod password_hash; pub mod secret_sharing; pub mod signature; @@ -223,8 +224,8 @@ use enums::{CiphertextSubtype, KeySubtype, PasswordHashSubtype, ShareSubtype, Si pub use header::{Header, HeaderType}; pub use enums::{ - CiphertextVersion, DataType, KeyVersion, PasswordHashVersion, SecretSharingVersion, - SignatureVersion, SigningKeyVersion, + CiphertextVersion, DataType, KeyVersion, OnlineCiphertextVersion, PasswordHashVersion, + SecretSharingVersion, SignatureVersion, SigningKeyVersion, }; pub use argon2::Variant as Argon2Variant; @@ -232,9 +233,7 @@ pub use argon2::Version as Argon2Version; pub use argon2parameters::defaults as argon2parameters_defaults; pub use argon2parameters::Argon2Parameters; pub use argon2parameters::Argon2ParametersBuilder; -pub use error::Error; - -pub type Result = std::result::Result; +pub use error::{Error, Result}; pub const DEFAULT_KEY_SIZE: usize = 32; pub const DEFAULT_PBKDF2_ITERATIONS: u32 = 10000; diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs new file mode 100644 index 000000000..20c00c2bb --- /dev/null +++ b/src/online_ciphertext/mod.rs @@ -0,0 +1,374 @@ +//! Module for symmetric/asymmetric encryption/decryption. +//! +//! This module contains everything related to encryption. You can use it to encrypt and decrypt data using either a shared key of a keypair. +//! Either way, the encryption will give you a `Ciphertext`, which has a method to decrypt it. +//! +//! ### Symmetric +//! +//! ```rust +//! use devolutions_crypto::utils::generate_key; +//! use devolutions_crypto::ciphertext::{ encrypt, CiphertextVersion, Ciphertext }; +//! +//! let key: Vec = generate_key(32); +//! +//! let data = b"somesecretdata"; +//! +//! let encrypted_data: Ciphertext = encrypt(data, &key, CiphertextVersion::Latest).expect("encryption shouldn't fail"); +//! +//! let decrypted_data = encrypted_data.decrypt(&key).expect("The decryption shouldn't fail"); +//! +//! assert_eq!(decrypted_data, data); +//! ``` +//! +//! ### Asymmetric +//! Here, you will need a `PublicKey` to encrypt data and the corresponding +//! `PrivateKey` to decrypt it. You can generate them by using `generate_keypair` +//! in the [Key module](#key). +//! +//! ```rust +//! use devolutions_crypto::key::{generate_keypair, KeyVersion, KeyPair}; +//! use devolutions_crypto::ciphertext::{ encrypt_asymmetric, CiphertextVersion, Ciphertext }; +//! +//! let keypair: KeyPair = generate_keypair(KeyVersion::Latest); +//! +//! let data = b"somesecretdata"; +//! +//! let encrypted_data: Ciphertext = encrypt_asymmetric(data, &keypair.public_key, CiphertextVersion::Latest).expect("encryption shouldn't fail"); +//! +//! let decrypted_data = encrypted_data.decrypt_asymmetric(&keypair.private_key).expect("The decryption shouldn't fail"); +//! +//! assert_eq!(decrypted_data, data); +//! ``` + +mod online_ciphertext_v1; + +use std::borrow::Borrow; + +use super::CiphertextSubtype; +use super::DataType; +use super::Error; +use super::Header; +use super::HeaderType; +pub use super::OnlineCiphertextVersion; +use super::Result; + +use super::key::{PrivateKey, PublicKey}; + +use online_ciphertext_v1::{OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor}; +use online_ciphertext_v1::{ + OnlineCiphertextV1Header, OnlineCiphertextV1HeaderAsymmetric, OnlineCiphertextV1HeaderSymmetric, +}; + +use paste::paste; + +impl OnlineCiphertextHeader { + pub fn into_decryptor(self, key: &[u8], aad: &[u8]) -> Result { + let mut full_aad: Vec = self.header.borrow().into(); + full_aad.extend_from_slice(aad); + + match self.payload { + OnlineCiphertextHeaderPayload::V1(header) => match header { + OnlineCiphertextV1Header::Symmetric(header) => { + let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header); + + Ok(OnlineCiphertextDecryptor::V1(cipher)) + } + _ => Err(Error::InvalidDataType), + }, + } + } + + pub fn into_decryptor_asymmetric( + self, + key: &PrivateKey, + aad: &[u8], + ) -> Result { + let mut full_aad: Vec = self.header.borrow().into(); + full_aad.extend_from_slice(aad); + + match self.payload { + OnlineCiphertextHeaderPayload::V1(header) => match header { + OnlineCiphertextV1Header::Asymmetric(header) => { + let cipher = OnlineCiphertextV1Decryptor::new_asymmetric(key, full_aad, header); + + Ok(OnlineCiphertextDecryptor::V1(cipher)) + } + _ => Err(Error::InvalidDataType), + }, + } + } + + pub fn get_serialized_size(&self) -> usize { + self.payload.get_serialized_size() + 8 + } + + pub fn get_chunk_size(&self) -> u32 { + self.payload.get_chunk_size() + } +} + +impl OnlineCiphertextEncryptor { + pub fn new( + key: &[u8], + aad: &[u8], + chunk_size: u32, + version: OnlineCiphertextVersion, + ) -> OnlineCiphertextEncryptor { + let mut header = Header:: { + data_subtype: CiphertextSubtype::Symmetric, + ..Default::default() + }; + + let mut full_aad: Vec = header.borrow().into(); + full_aad.extend_from_slice(aad); + + match version { + OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { + header.version = OnlineCiphertextVersion::V1; + + let cipher = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); + + OnlineCiphertextEncryptor::V1(cipher) + } + } + } + + pub fn new_asymmetric( + public_key: &PublicKey, + aad: &[u8], + chunk_size: u32, + version: OnlineCiphertextVersion, + ) -> OnlineCiphertextEncryptor { + let mut header = Header:: { + data_subtype: CiphertextSubtype::Asymmetric, + ..Default::default() + }; + + let mut full_aad: Vec = header.borrow().into(); + full_aad.extend_from_slice(aad); + + match version { + OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { + header.version = OnlineCiphertextVersion::V1; + + let cipher = + OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size); + + OnlineCiphertextEncryptor::V1(cipher) + } + } + } +} + +macro_rules! online_ciphertext_header_impl { + ($($version_name:ident),+) => { + paste! { + /// A versionned online ciphertext. + #[derive(Clone, Debug)] + pub struct OnlineCiphertextHeader { + pub(crate) header: Header, + payload: OnlineCiphertextHeaderPayload, + } + + impl HeaderType for OnlineCiphertextHeader { + type Version = OnlineCiphertextVersion; + type Subtype = CiphertextSubtype; + + fn data_type() -> DataType { + DataType::OnlineCiphertext + } + } + + #[derive(Clone, Debug)] + enum OnlineCiphertextHeaderPayload { + $( + $version_name([]), + ),+ + } + + impl OnlineCiphertextHeaderPayload { + pub fn get_serialized_size(&self) -> usize { + match &self { + $( + Self::$version_name(p) => p.get_serialized_size(), + ),+ + } + } + + pub fn get_chunk_size(&self) -> u32 { + match &self { + $( + Self::$version_name(p) => p.get_chunk_size(), + ),+ + } + } + } + + impl From<&OnlineCiphertextHeader> for Vec { + fn from(value: &OnlineCiphertextHeader) -> Self { + let mut output: Vec = value.header.borrow().into(); + let mut payload: Vec = match &value.payload { + $( + OnlineCiphertextHeaderPayload::$version_name(p) => { + p.into() + }, + ),+ + }; + + output.append(&mut payload); + output + } + } + + impl TryFrom<&[u8]> for OnlineCiphertextHeader { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let header = Header::::try_from(&value[..8])?; + + let version = if header.version == OnlineCiphertextVersion::Latest { OnlineCiphertextVersion::V1 } else { header.version }; + match version { + $( + OnlineCiphertextVersion::$version_name => { + match header.data_subtype { + CiphertextSubtype::Symmetric => { + Ok(Self { + header, + payload: + OnlineCiphertextHeaderPayload::$version_name + ([]::Symmetric + ([]::try_from(&value[8..])?)) + }) + } + CiphertextSubtype::Asymmetric => { + Ok(Self { + header, + payload: + OnlineCiphertextHeaderPayload::$version_name + ([]::Asymmetric + ([]::try_from(&value[8..])?)) + }) + } + CiphertextSubtype::None => Err(Error::UnknownSubtype) + } + } + ),+ + OnlineCiphertextVersion::Latest => unreachable!("Latest is checked before the match arm") + } + } + } + } + }; +} + +macro_rules! online_ciphertext_impl { + ($name:ident, $func:ident, $($version_name:ident),+) => { + paste! { + pub enum [] { + $( + $version_name([]), + ),+ + } + + impl [] { + pub fn get_chunk_size(&self) -> u32 { + match &self { + $( + []::$version_name(cipher) => { + cipher.get_chunk_size() + } + ),+ + } + } + + pub fn get_tag_size(&self) -> usize { + match &self { + $( + []::$version_name(cipher) => { + cipher.get_tag_size() + } + ),+ + } + } + + pub fn get_header(&self) -> OnlineCiphertextHeader { + match self { + $( + Self::$version_name(encryptor) => { + let data_subtype = encryptor.get_header().get_subtype(); + OnlineCiphertextHeader { + header: Header:: { + data_subtype, + ..Default::default() + }, + payload: OnlineCiphertextHeaderPayload::$version_name(encryptor.get_header().clone()), + } + } + ),+ + } + } + + pub fn [<$func _next_chunk>]( + &mut self, + data: &[u8], + aad: &[u8], + ) -> Result> { + match self { + $( + []::$version_name(cipher) => { + cipher.[<$func _next_chunk>](data, aad) + } + ),+ + } + } + + pub fn [<$func _next_chunk_in_place>]( + &mut self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + match self { + $( + []::$version_name(cipher) => { + cipher.[<$func _next_chunk_in_place>](data, aad) + } + ),+ + } + } + + pub fn [<$func _last_chunk>]( + self, + data: &[u8], + aad: &[u8], + ) -> Result> { + match self { + $( + []::$version_name(cipher) => { + cipher.[<$func _last_chunk>](data, aad) + } + ),+ + } + } + + pub fn [<$func _last_chunk_in_place>]( + self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + match self { + $( + []::$version_name(cipher) => { + cipher.[<$func _last_chunk_in_place>](data, aad) + } + ),+ + } + } + } + } + }; +} + +online_ciphertext_impl!(Encryptor, encrypt, V1); +online_ciphertext_impl!(Decryptor, decrypt, V1); + +online_ciphertext_header_impl!(V1); diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs new file mode 100644 index 000000000..70f21613b --- /dev/null +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -0,0 +1,395 @@ +//! Online Ciphertext V1: STREAM-LE31-XChaCha20Poly1305 +use super::{PrivateKey, PublicKey}; +use crate::enums::CiphertextSubtype; + +use super::Error; +use super::Result; + +use std::borrow::Borrow; + +use chacha20poly1305::aead::{ + stream::{DecryptorLE31, EncryptorLE31}, + Payload, +}; +use chacha20poly1305::{KeyInit, XChaCha20Poly1305}; + +use rand::{rngs::OsRng, RngCore}; +use x25519_dalek::StaticSecret; +use zeroize::Zeroizing; + +use paste::paste; + +/// Context string for the Blake3 KDF function. +/// This is used to normalize the key length and domain separation +const CONTEXT: &str = "devolutions_crypto online_ciphertext_v1"; + +#[derive(Clone, Debug)] +pub enum OnlineCiphertextV1Header { + Symmetric(OnlineCiphertextV1HeaderSymmetric), + Asymmetric(OnlineCiphertextV1HeaderAsymmetric), +} + +#[derive(Clone, Debug)] +pub struct OnlineCiphertextV1HeaderSymmetric { + chunk_size: u32, + nonce: [u8; 20], +} + +#[derive(Clone, Debug)] +pub struct OnlineCiphertextV1HeaderAsymmetric { + chunk_size: u32, + nonce: [u8; 20], + public_key: x25519_dalek::PublicKey, +} + +impl OnlineCiphertextV1Header { + pub fn get_serialized_size(&self) -> usize { + match self { + Self::Symmetric(_) => { + // chunk_size (u32, so 4 bytes) + 20 bytes nonce + 20 + 4 + } + Self::Asymmetric(_) => { + // chunk_size (u32, so 4 bytes) + 20 bytes nonce + 32 bytes public key + 20 + 4 + 32 + } + } + } + + pub fn get_chunk_size(&self) -> u32 { + match self { + Self::Symmetric(x) => x.chunk_size, + Self::Asymmetric(x) => x.chunk_size, + } + } + + pub fn get_subtype(&self) -> CiphertextSubtype { + match self { + Self::Symmetric(_) => CiphertextSubtype::Symmetric, + Self::Asymmetric(_) => CiphertextSubtype::Asymmetric, + } + } +} + +impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { + /// Serialize the header into bytes + fn from(value: &OnlineCiphertextV1HeaderSymmetric) -> Self { + let mut buf = value.chunk_size.to_le_bytes().to_vec(); + buf.extend(value.nonce); + + buf + } +} + +impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { + /// Serialize the header into bytes + fn from(value: &OnlineCiphertextV1HeaderAsymmetric) -> Self { + let mut buf = value.chunk_size.to_le_bytes().to_vec(); + buf.extend(value.nonce); + buf.extend_from_slice(value.public_key.as_bytes()); + + buf + } +} + +impl From<&OnlineCiphertextV1Header> for Vec { + fn from(value: &OnlineCiphertextV1Header) -> Self { + match value { + OnlineCiphertextV1Header::Symmetric(x) => x.into(), + OnlineCiphertextV1Header::Asymmetric(x) => x.into(), + } + } +} + +impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderSymmetric { + type Error = Error; + + /// Parse a header from a byte array + fn try_from(value: &[u8]) -> Result { + if value.len() != 24 { + return Err(Error::InvalidLength); + } + + let (chunk_size, nonce) = value.split_at(4); + + let chunk_size: [u8; 4] = chunk_size + .try_into() + .expect("size is hardcoded and should always be right"); + let chunk_size = u32::from_le_bytes(chunk_size); + + let nonce = nonce + .try_into() + .expect("Length is checked at the start of the function"); + + Ok(Self { chunk_size, nonce }) + } +} + +impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderAsymmetric { + type Error = Error; + + /// Parse a header from a byte array + fn try_from(value: &[u8]) -> Result { + if value.len() != 24 + 32 { + return Err(Error::InvalidLength); + } + + let (chunk_size, value) = value.split_at(4); + + let chunk_size: [u8; 4] = chunk_size + .try_into() + .expect("size is hardcoded and should always be right"); + let chunk_size = u32::from_le_bytes(chunk_size); + + let (nonce, public_key) = value.split_at(20); + + let nonce = nonce + .try_into() + .expect("size is hardcoded and should always be right"); + + let public_key: [u8; 32] = public_key + .try_into() + .expect("size is checked at the start of the function"); + let public_key = x25519_dalek::PublicKey::from(public_key); + + Ok(Self { + chunk_size, + nonce, + public_key, + }) + } +} + +/// Implements the encryptor/decryptor structure +macro_rules! online_ciphertext_impl { + ($struct_name:ident, $cipher_name:ident, $func:ident) => { + pub struct $struct_name { + header: OnlineCiphertextV1Header, + aad: Vec, + cipher: $cipher_name, + } + + impl $struct_name { + /// Gets the number of bytes to process in each chunk + pub fn get_chunk_size(&self) -> u32 { + self.header.get_chunk_size() + } + + pub fn get_tag_size(&self) -> usize { + 16 + } + + pub fn get_header(&self) -> &OnlineCiphertextV1Header { + &self.header + } + + paste! { + /// Process a single chunk + pub fn [<$func _next_chunk>]( + &mut self, + data: &[u8], + aad: &[u8], + ) -> Result> { + if data.len() as u32 != self.header.get_chunk_size() && data.len() as u32 != (self.header.get_chunk_size() + self.get_tag_size() as u32){ + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + let payload = Payload { + msg: &data, + aad: &full_aad, + }; + + Ok(self.cipher.[<$func _next>](payload)?) + } + + /// Process a single chunk in place. + /// Requires a Vec because it needs to be expandable to accomodate the tag. + pub fn [<$func _next_chunk_in_place>]( + &mut self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + if data.len() as u32 != self.header.get_chunk_size() && data.len() as u32 != (self.header.get_chunk_size() + self.get_tag_size() as u32){ + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + self.cipher.[<$func _next_in_place>](&full_aad, data)?; + + Ok(()) + } + + /// Process the last chunk. + pub fn [<$func _last_chunk>]( + self, + data: &[u8], + aad: &[u8], + ) -> Result> { + if (data.len() as u32) > (self.header.get_chunk_size() + self.get_tag_size() as u32) { + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + let payload = Payload { + msg: &data, + aad: &full_aad, + }; + + Ok(self.cipher.[<$func _last>](payload)?) + } + + /// Process a single chunk in place. + /// Requires a Vec because it needs to be expandable to accomodate the tag. + pub fn [<$func _last_chunk_in_place>]( + self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + if (data.len() as u32) > (self.header.get_chunk_size() + self.get_tag_size() as u32) { + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + self.cipher.[<$func _last_in_place>](&full_aad, data)?; + + Ok(()) + } + } + } + }; +} + +impl OnlineCiphertextV1Encryptor { + /// Creates a new encryptor and the corresponding header + pub fn new(key: &[u8], mut aad: Vec, chunk_size: u32) -> Self { + // Generate a new nonce + let mut nonce = [0u8; 20]; + OsRng.fill_bytes(&mut nonce); + + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); + + // Create the STREAM encryptor + let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); + + // Create aad + let header = OnlineCiphertextV1HeaderSymmetric { chunk_size, nonce }; + + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); + + Self { + header: OnlineCiphertextV1Header::Symmetric(header), + aad, + cipher, + } + } + + pub fn new_asymmetric(public_key: &PublicKey, mut aad: Vec, chunk_size: u32) -> Self { + // Perform a ECDH exchange as per ECIES + let public_key = x25519_dalek::PublicKey::from(public_key); + + let ephemeral_private_key = StaticSecret::random_from_rng(rand_core::OsRng); + let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_private_key); + + let key = ephemeral_private_key.diffie_hellman(&public_key); + + // Generate a new nonce + let mut nonce = [0u8; 20]; + OsRng.fill_bytes(&mut nonce); + + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes())); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); + + // Create the STREAM encryptor + let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); + + let header = OnlineCiphertextV1HeaderAsymmetric { + chunk_size, + nonce, + public_key: ephemeral_public_key, + }; + + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); + + Self { + header: OnlineCiphertextV1Header::Asymmetric(header), + cipher, + aad, + } + } +} + +impl OnlineCiphertextV1Decryptor { + pub fn new(key: &[u8], mut aad: Vec, header: OnlineCiphertextV1HeaderSymmetric) -> Self { + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); + + // Create the STREAM decryptor + let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); + + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); + + Self { + header: OnlineCiphertextV1Header::Symmetric(header), + aad, + cipher, + } + } + + pub fn new_asymmetric( + private_key: &PrivateKey, + mut aad: Vec, + header: OnlineCiphertextV1HeaderAsymmetric, + ) -> Self { + // Perform a ECDH exchange as per ECIES + let private_key = x25519_dalek::StaticSecret::from(private_key); + + let key = private_key.diffie_hellman(&header.public_key); + + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes())); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); + + // Create the STREAM decryptor + let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); + + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); + + Self { + header: OnlineCiphertextV1Header::Asymmetric(header), + aad, + cipher, + } + } +} + +online_ciphertext_impl!(OnlineCiphertextV1Encryptor, EncryptorLE31, encrypt); +online_ciphertext_impl!(OnlineCiphertextV1Decryptor, DecryptorLE31, decrypt); diff --git a/src/password_hash/mod.rs b/src/password_hash/mod.rs index 0bef56caf..0640236f9 100644 --- a/src/password_hash/mod.rs +++ b/src/password_hash/mod.rs @@ -24,6 +24,7 @@ use super::Result; use password_hash_v1::PasswordHashV1; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -112,7 +113,7 @@ impl PasswordHash { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: PasswordHash) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/secret_sharing/mod.rs b/src/secret_sharing/mod.rs index 359ba59e9..dc1b44861 100644 --- a/src/secret_sharing/mod.rs +++ b/src/secret_sharing/mod.rs @@ -32,6 +32,7 @@ use super::ShareSubtype; use secret_sharing_v1::ShareV1; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -150,7 +151,7 @@ where impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: Share) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/signature/mod.rs b/src/signature/mod.rs index af852b8ca..85ce2109c 100644 --- a/src/signature/mod.rs +++ b/src/signature/mod.rs @@ -38,6 +38,7 @@ use super::signing_key::{SigningKeyPair, SigningPublicKey}; use signature_v1::SignatureV1; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -121,7 +122,7 @@ impl Signature { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: Signature) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/signature/signature_v1.rs b/src/signature/signature_v1.rs index c442faadb..79e063765 100644 --- a/src/signature/signature_v1.rs +++ b/src/signature/signature_v1.rs @@ -19,8 +19,12 @@ pub struct SignatureV1 { #[cfg(feature = "fuzz")] impl Arbitrary for SignatureV1 { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let signature: [u8; 32] = Arbitrary::arbitrary(u)?; - Ok(Self { signature }) + let mut signature = [0u8; 64]; + u.fill_buffer(&mut signature)?; + + Ok(Self { + signature: Signature::from_bytes(&signature), + }) } } diff --git a/src/signing_key/mod.rs b/src/signing_key/mod.rs index 924cc8b29..d926fecb3 100644 --- a/src/signing_key/mod.rs +++ b/src/signing_key/mod.rs @@ -33,6 +33,7 @@ pub use super::SigningKeyVersion; use signing_key_v1::{SigningKeyV1Pair, SigningKeyV1Public}; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -153,7 +154,7 @@ impl SigningKeyPair { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: SigningPublicKey) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header @@ -189,7 +190,7 @@ impl TryFrom<&[u8]> for SigningPublicKey { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: SigningKeyPair) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/signing_key/signing_key_v1.rs b/src/signing_key/signing_key_v1.rs index b032850ed..95008a1d4 100644 --- a/src/signing_key/signing_key_v1.rs +++ b/src/signing_key/signing_key_v1.rs @@ -31,10 +31,13 @@ impl core::fmt::Debug for SigningKeyV1Pair { #[cfg(feature = "fuzz")] impl Arbitrary for SigningKeyV1Pair { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let keypair: [u8; 64] = Arbitrary::arbitrary(u)?; - Ok(Self { - keypair: Keypair::from(private_key), - }) + let mut keypair = [0u8; 64]; + u.fill_buffer(&mut keypair)?; + + match SigningKey::from_keypair_bytes(&keypair) { + Ok(keypair) => Ok(Self { keypair }), + Err(_) => Err(arbitrary::Error::IncorrectFormat), + } } } @@ -47,9 +50,10 @@ pub struct SigningKeyV1Public { impl Arbitrary for SigningKeyV1Public { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let public_key: [u8; 32] = Arbitrary::arbitrary(u)?; - Ok(Self { - key: PublicKey::from(public_key), - }) + match VerifyingKey::from_bytes(&public_key) { + Ok(key) => Ok(Self { key }), + Err(_) => Err(arbitrary::Error::IncorrectFormat), + } } } diff --git a/src/utils.rs b/src/utils.rs index 76f85cc52..9c7d2fc00 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,6 +7,8 @@ use rand::{rngs::OsRng, RngCore}; use sha2::Sha256; use subtle::ConstantTimeEq as _; +use crate::online_ciphertext::OnlineCiphertextHeader; + use super::Argon2Parameters; use super::DataType; use super::Error; @@ -117,10 +119,14 @@ pub fn validate_header(data: &[u8], data_type: DataType) -> bool { } DataType::Share => Header::::try_from(&data[0..Header::len()]).is_ok(), DataType::Signature => Header::::try_from(&data[0..Header::len()]).is_ok(), + DataType::OnlineCiphertext => { + Header::::try_from(&data[0..Header::len()]).is_ok() + } } } /// Temporarly binded here for a specific use case, don't rely on this. +/// /// Copied and modified from: /// https://github.com/RustCrypto/password-hashing/blob/master/scrypt/src/simple.rs /// Because rand is outdated, I cannot use the crate directly diff --git a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl index 81a785044..44043c362 100644 --- a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl +++ b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl @@ -6,6 +6,7 @@ enum DataType { "Share", "SigningKey", "Signature", + "OnlineCiphertext", }; enum CiphertextVersion { @@ -68,6 +69,8 @@ enum DevolutionsCryptoError { "IoError", "NotEnoughShares", "InconsistentVersion", + "InvalidChunkLength", + "PoisonedMutex", }; interface Argon2ParametersBuilder { diff --git a/wrappers/csharp/src/DecryptionStream.cs b/wrappers/csharp/src/DecryptionStream.cs new file mode 100644 index 000000000..48e88e50c --- /dev/null +++ b/wrappers/csharp/src/DecryptionStream.cs @@ -0,0 +1,244 @@ +using System; +using System.IO; + +namespace Devolutions.Cryptography +{ + public class DecryptionStream + : Stream, IDisposable + { + private UIntPtr _native_ptr = UIntPtr.Zero; + private readonly int _chunkLength; + private readonly int _tagLength; + private bool _finalBlockTransformed = false; + private readonly bool _leaveOpen; + + public int ChunkLength { get { return _chunkLength; } } + + public int TagLength { get { return _tagLength; } } + + public bool HasFlushedFinalBlock + { + get { return _finalBlockTransformed; } + } + + private readonly byte[] _inputBuffer; + + private int _inputBufferOffset = 0; + private bool _disposed = false; + + private readonly Stream _outputStream; + + public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream) + : this(key, aad, header, asymmetric, outputStream, false) { } + + public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream, bool leaveOpen) + { + _outputStream = outputStream; + _leaveOpen = leaveOpen; + + long result = Native.NewOnlineDecryptor(key, (UIntPtr)key.Length, aad, (UIntPtr)aad.Length, header, (UIntPtr)header.Length, asymmetric, ref _native_ptr); + + if (result < 0) + { + Utils.HandleError(result); + } + + long tagSize = Native.OnlineDecryptorGetTagSize(_native_ptr); + + if (tagSize < 0) + { + Utils.HandleError(tagSize); + } + + long chunkLength = Native.OnlineDecryptorGetChunkSize(_native_ptr); + + if (tagSize < 0) + { + Utils.HandleError(chunkLength); + } + + _tagLength = (int)tagSize; + _chunkLength = (int)chunkLength + _tagLength; + + _inputBuffer = new byte[_chunkLength]; + } + + public byte[] GetHeader() + { + long headerSize = Native.OnlineDecryptorGetHeaderSize(_native_ptr); + + if (headerSize < 0) + { + Utils.HandleError(headerSize); + } + + byte[] header = new byte[headerSize]; + + long result = Native.OnlineDecryptorGetHeader(_native_ptr, header, (UIntPtr)headerSize); + + if (result < 0) + { + Utils.HandleError(result); + } + + return header; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public void FlushFinalBlock() + { + if (HasFlushedFinalBlock) + { + throw new NotSupportedException(); + } + + if (_inputBufferOffset > 0) + { + byte[] outputBuffer = DecryptLastChunk(); + + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); + } + + Array.Clear(_inputBuffer, 0, _inputBuffer.Length); + + _finalBlockTransformed = true; + } + + public override void Flush() + { + return; + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (HasFlushedFinalBlock) + { + // Cannot write as stream is already closed + return; + }; + + while (count > ChunkLength - _inputBufferOffset) + { + // Here we write every finished blocks + int countToAdd = ChunkLength - _inputBufferOffset; + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, countToAdd); + + // Encrypt the buffer + byte[] outputBuffer = DecryptChunk(); + + // Write the output to the stream + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); + + count -= countToAdd; + offset += countToAdd; + _inputBufferOffset = 0; + } + + if (count > 0) + { + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, count); + + _inputBufferOffset += count; + } + } + + private byte[] DecryptChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[ChunkLength - TagLength]; + + long result = Native.OnlineDecryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr)ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + + if (result < 0) + { + Utils.HandleError(result); + } + + return outputBuffer; + } + + private byte[] DecryptLastChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[_inputBufferOffset - TagLength]; + + long result = Native.OnlineDecryptorLastChunk(_native_ptr, _inputBuffer, (UIntPtr)_inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + + if (result < 0) + { + Utils.HandleError(result); + } + + // Here, the pointer is freed, so let's set it to 0 + _native_ptr = UIntPtr.Zero; + + return outputBuffer; + } + + public new void Dispose() + { + base.Dispose(); + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (!HasFlushedFinalBlock) + { + FlushFinalBlock(); + }; + + if (!_leaveOpen) + { + _outputStream.Close(); + } + } + base.Dispose(disposing: disposing); + + // If the ptr has not been freed yet, do it now + FreeNativeObject(); + + _disposed = true; + } + } + + private void FreeNativeObject() + { + if (_native_ptr != UIntPtr.Zero) + { + Native.FreeOnlineDecryptor(_native_ptr); + _native_ptr = UIntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/wrappers/csharp/src/EncryptionStream.cs b/wrappers/csharp/src/EncryptionStream.cs new file mode 100644 index 000000000..43da6d1eb --- /dev/null +++ b/wrappers/csharp/src/EncryptionStream.cs @@ -0,0 +1,237 @@ +using System; +using System.IO; + +namespace Devolutions.Cryptography +{ + public class EncryptionStream + : Stream, IDisposable + { + private UIntPtr _native_ptr = UIntPtr.Zero; + private readonly int _chunkLength; + private readonly int _tagLength; + private bool _finalBlockTransformed = false; + private readonly bool _leaveOpen; + + public int ChunkLength { get { return _chunkLength; } } + + public int TagLength { get { return _tagLength; } } + + public bool HasFlushedFinalBlock + { + get { return _finalBlockTransformed; } + } + + private readonly byte[] _inputBuffer; + + private int _inputBufferOffset = 0; + private bool _disposed = false; + + private readonly Stream _outputStream; + + public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream) + : this(key, aad, chunkLength, asymmetric, version, outputStream, false) { } + + public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream, bool leaveOpen) + { + _chunkLength = chunkLength; + _outputStream = outputStream; + _leaveOpen = leaveOpen; + + long result = Native.NewOnlineEncryptor(key, (UIntPtr)key.Length, aad, (UIntPtr)aad.Length, (uint)chunkLength, asymmetric, (ushort)version, ref _native_ptr); + + if (result < 0) + { + Utils.HandleError(result); + } + + long tagSize = Native.OnlineEncryptorGetTagSize(_native_ptr); + + if (tagSize < 0) + { + Utils.HandleError(tagSize); + } + + _tagLength = (int)tagSize; + + _inputBuffer = new byte[chunkLength]; + } + + public byte[] GetHeader() + { + long headerSize = Native.OnlineEncryptorGetHeaderSize(_native_ptr); + + if (headerSize < 0) + { + Utils.HandleError(headerSize); + } + + byte[] header = new byte[headerSize]; + + long result = Native.OnlineEncryptorGetHeader(_native_ptr, header, (UIntPtr)headerSize); + + if (result < 0) + { + Utils.HandleError(result); + } + + return header; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public void FlushFinalBlock() + { + if (HasFlushedFinalBlock) + { + throw new NotSupportedException(); + } + + if (_inputBufferOffset > 0) + { + byte[] outputBuffer = EncryptLastChunk(); + + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); + } + + Array.Clear(_inputBuffer, 0, _inputBuffer.Length); + + _finalBlockTransformed = true; + } + + public override void Flush() + { + return; + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (HasFlushedFinalBlock) + { + // Cannot write as stream is already closed + return; + }; + + while (count > ChunkLength - _inputBufferOffset) + { + // Here we write every finished blocks + int countToAdd = ChunkLength - _inputBufferOffset; + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, countToAdd); + + // Encrypt the buffer + byte[] outputBuffer = EncryptChunk(); + + // Write the output to the stream + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); + + count -= countToAdd; + offset += countToAdd; + _inputBufferOffset = 0; + } + + if (count > 0) + { + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, count); + + _inputBufferOffset += count; + } + } + + private byte[] EncryptChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[ChunkLength + TagLength]; + + long result = Native.OnlineEncryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr)ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + + if (result < 0) + { + Utils.HandleError(result); + } + + return outputBuffer; + } + + private byte[] EncryptLastChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[_inputBufferOffset + TagLength]; + + long result = Native.OnlineEncryptorLastChunk(_native_ptr, _inputBuffer, (UIntPtr)_inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + + if (result < 0) + { + Utils.HandleError(result); + } + + // Here, the pointer is freed, so let's set it to 0 + _native_ptr = UIntPtr.Zero; + + return outputBuffer; + } + + public new void Dispose() + { + base.Dispose(); + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (!HasFlushedFinalBlock) + { + FlushFinalBlock(); + }; + + if (!_leaveOpen) + { + _outputStream.Close(); + } + } + base.Dispose(disposing: disposing); + + // If the ptr has not been freed yet, do it now + FreeNativeObject(); + + _disposed = true; + } + } + + private void FreeNativeObject() + { + if (_native_ptr != UIntPtr.Zero) + { + Native.FreeOnlineEncryptor(_native_ptr); + _native_ptr = UIntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/wrappers/csharp/src/Enums.cs b/wrappers/csharp/src/Enums.cs index ffd712fc6..e37d54c2a 100644 --- a/wrappers/csharp/src/Enums.cs +++ b/wrappers/csharp/src/Enums.cs @@ -42,6 +42,11 @@ public enum DataType /// A wrapped signature /// Signature = 6, + + /// + /// TODO + /// + OnlineCiphertext = 7 } /// diff --git a/wrappers/csharp/src/Native.Core.cs b/wrappers/csharp/src/Native.Core.cs index 903e8e631..6ea4dbe98 100644 --- a/wrappers/csharp/src/Native.Core.cs +++ b/wrappers/csharp/src/Native.Core.cs @@ -101,6 +101,54 @@ public static partial class Native [DllImport(LibName, EntryPoint = "MixKeyExchangeSize", CallingConvention = CallingConvention.Cdecl)] internal static extern long MixKeyExchangeSizeNative(); + [DllImport(LibName, EntryPoint = "NewOnlineEncryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long NewOnlineEncryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, UInt32 chunkLength, bool asymmetric, UInt16 version, ref UIntPtr output); + + [DllImport(LibName, EntryPoint = "NewOnlineDecryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long NewOnlineDecryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, byte[] header, UIntPtr headerLength, bool asymmetric, ref UIntPtr output); + + [DllImport(LibName, EntryPoint = "FreeOnlineEncryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long FreeOnlineEncryptor(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "FreeOnlineDecryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long FreeOnlineDecryptor(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetChunkSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetChunkSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetChunkSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetChunkSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetHeader", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetHeader(UIntPtr ptr, byte[] result, UIntPtr resultLength); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetHeader", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetHeader(UIntPtr ptr, byte[] result, UIntPtr resultLength); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetHeaderSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetHeaderSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetHeaderSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetHeaderSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetTagSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetTagSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetTagSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetTagSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorNextChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorNextChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorNextChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorNextChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorLastChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorLastChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorLastChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorLastChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + [DllImport(LibName, EntryPoint = "ValidateHeader", CallingConvention = CallingConvention.Cdecl)] internal static extern long ValidateHeader(byte[] data, UIntPtr dataLength, ushort dataType); diff --git a/wrappers/csharp/src/NativeError.cs b/wrappers/csharp/src/NativeError.cs index 532bc402f..0b13d9465 100644 --- a/wrappers/csharp/src/NativeError.cs +++ b/wrappers/csharp/src/NativeError.cs @@ -84,5 +84,15 @@ public enum NativeError /// The version of the multiple data is inconsistent: -42 /// InconsistentVersion = -42, + + /// + /// The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size: -43 + /// + InvalidChunkLength = -43, + + /// + /// The mutex is poisoned and cannot be locked: -44 + /// + PoisonedMutex = -44, } -} \ No newline at end of file +} diff --git a/wrappers/csharp/tests/unit-tests/TestData.cs b/wrappers/csharp/tests/unit-tests/TestData.cs index 823c4a5ef..a77b8431a 100644 --- a/wrappers/csharp/tests/unit-tests/TestData.cs +++ b/wrappers/csharp/tests/unit-tests/TestData.cs @@ -18,6 +18,12 @@ public static class TestData public const string Base64TestData2 = "QUJDDE"; + public const string Base64TestDataStream = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gTnVsbGFtIHF1YW0gbnVuYywgcGxhY2VyYXQgbWF4aW11cyBmYWNpbGlzaXMgZGlnbmlzc2ltLCB0aW5jaWR1bnQgaW4gdHVycGlzLiBEdWlzIHRlbXBvciBtYWduYSBldSBjb252YWxsaXMgbWF0dGlzLiBOdWxsYW0gcmhvbmN1cyBsYWN1cyBub24gbmliaCBtb2xlc3RpZSBzb2RhbGVzLiBJbiBub24gdHJpc3RpcXVlIG51bmMuIE51bGxhIGlkIGdyYXZpZGEgbnVuYy4gRG9uZWMgZWdlc3RhcyBtaSBlZ2V0IHRlbGx1cyBlZmZpY2l0dXIsIG1vbGxpcyBsb2JvcnRpcyB1cm5hIHRyaXN0aXF1ZS4gVml2YW11cyBzZWQgdGVsbHVzIHZpdGFlIG1ldHVzIG1hdHRpcyBsYWNpbmlhLiBWZXN0aWJ1bHVtIGRhcGlidXMgZXN0IGV1IHB1bHZpbmFyIGxhb3JlZXQuIEV0aWFtIGNvbW1vZG8gZXJvcyBhdCBmYWNpbGlzaXMgbW9sbGlzLiBDdXJhYml0dXIgcnV0cnVtIHRpbmNpZHVudCBzZW0sIHZpdGFlIHBsYWNlcmF0IG51bmMgbGFjaW5pYSBuZWMuIE9yY2kgdmFyaXVzIG5hdG9xdWUgcGVuYXRpYnVzIGV0IG1hZ25pcyBkaXMgcGFydHVyaWVudCBtb250ZXMsIG5hc2NldHVyIHJpZGljdWx1cyBtdXMuIFBoYXNlbGx1cyBjdXJzdXMgYXVndWUgdXQgbmlzbCB0ZW1wdXMgbGFjaW5pYS4gCk51bGxhIHZpdGFlIGZlcm1lbnR1bSBlc3QsIGV0IHZlaGljdWxhIG1hdXJpcy4gSW50ZXJkdW0gZXQgbWFsZXN1YWRhIGZhbWVzIGFjIGFudGUgaXBzdW0gcHJpbWlzIGluIGZhdWNpYnVzLiBQaGFzZWxsdXMgc2l0IGFtZXQgbWFnbmEgcGVsbGVudGVzcXVlLCBtYXhpbXVzIGp1c3RvIHZpdGFlLCBtYWxlc3VhZGEgYW50ZS4gVmVzdGlidWx1bSBhbnRlIGlwc3VtIHByaW1pcyBpbiBmYXVjaWJ1cyBvcmNpIGx1Y3R1cyBldCB1bHRyaWNlcyBwb3N1ZXJlIGN1YmlsaWEgY3VyYWU7IFN1c3BlbmRpc3NlIHRlbXB1cyBkb2xvciBldSBhdWd1ZSByaG9uY3VzIHBoYXJldHJhLiBEb25lYyB2aXRhZSBuaXNpIHBlbGxlbnRlc3F1ZSwgY29udmFsbGlzIG9kaW8gYXQsIGludGVyZHVtIGF1Z3VlLiBQZWxsZW50ZXNxdWUgYXQgcHVydXMgYSBuaWJoIGxhY2luaWEgdWxsYW1jb3JwZXIgYWMgYSBudWxsYS4gQWVuZWFuIG5pYmggbGlndWxhLCBoZW5kcmVyaXQgaW4gbHVjdHVzIGV0LCBzb2RhbGVzIG5vbiB0dXJwaXMuIE51bGxhIGZhY2lsaXNpLiBOdWxsYSBsYW9yZWV0IG1hc3NhIGZlbGlzLCBhIGRpY3R1bSBsaWd1bGEgYmxhbmRpdCB1dC4gClNlZCBzb2RhbGVzIHJpc3VzIGp1c3RvLCB1dCBmZXJtZW50dW0gc2FwaWVuIGRpY3R1bSBzZWQuIFNlZCB1bHRyaWNlcyB2ZWxpdCBldCBmZWxpcyBmZXJtZW50dW0gaW50ZXJkdW0uIFZlc3RpYnVsdW0gY29uc2VjdGV0dXIgbGFvcmVldCBpcHN1bS4gUGVsbGVudGVzcXVlIGhhYml0YW50IG1vcmJpIHRyaXN0aXF1ZSBzZW5lY3R1cyBldCBuZXR1cyBldCBtYWxlc3VhZGEgZmFtZXMgYWMgdHVycGlzIGVnZXN0YXMuIENyYXMgaW4gZXggc3VzY2lwaXQsIGFjY3Vtc2FuIHB1cnVzIHZpdGFlLCByaG9uY3VzIGVsaXQuIE1hZWNlbmFzIHF1aXMgc2FwaWVuIG5vbiBxdWFtIGFjY3Vtc2FuIHZlaGljdWxhIHNlZCB2aXRhZSBwdXJ1cy4gUHJvaW4gY29uc2VxdWF0IGlwc3VtIGluIGxhY3VzIGxvYm9ydGlzIHRlbXB1cy4gRG9uZWMgYWMgYWxpcXVldCBtYXNzYS4g"; + + public const string Base64HeaderDataStreamEncrypted = "DQwHAAEAAADoAwAAOU63lZRBVkAapzo92zfg/KZy6/o="; + + public const string Base64TestDataStreamEncrypted = "OF1pXe1lexpcY4iBF8Vq5CGtRhK9QyvYar8U5fLHxZpnzEeUDAW7LbxoOUhjQqRW+VMnACB61r/FLNMUz0dVxcuvV1bZj1RxO3x/4xCybMMRc394UyG08KdI3+FAveGbjBG3r2G49RtqAS+IJb31VAIbuEmxkiEG0KgQlEKp9ZXevJGZwlG5VT9RIiKYtJ1FB1/crTSuANtZhSfVvO/iGc7TnTI+Fd23/GHY8/aFAeL9AAnG6qp+X/N4l0fP1klZFlP/0A+Q9xshz4fZ8N0RHjSrEU2YbV79LnLIwBd1U0NPArVUmiJd2Nblmc/6dfH9wB6i0VQYzk5yz4UQZ/n/9aF2h9jkMjpUVPyDwq+Eelzqb6ZJUNn1bpontHqe7PBCBj5GHTBt8FjOI92ZIpHte9OKk3i/Ni1HmqGdjVmne1oP023x9IX9mX4+i8XeCwn+RQ9xmoYRgdK2UzSs9KSyA9dOhQyH5rXF6gL7QtSgDsPa6uVuuk5NnrNyTNanTTUkc1W612WzcKPRpSOH+IeIGPUuEMju5X0qsDPRziNg5P78z8QDNlGYe4r+d7uYTiQsFhqsl7/GgyXFIJbEKtpUuhpk82nSZAyhF1jRv4pdU5hI9rg0FOo9E0S/iE5UkICUfHRpLw9X2PaJIzyIuIHoTJ/BITt1FZIk19T+IuC3ITSc/ziP3ptRjR9EK5hSyVXdWEn+N5ac/umeHsf5/nY+H3FN7Z1EzNG5Jglb5iOECy3NWted0jggPJcJnDsQCWuig2AY7dU8dA9IpmDFMT8bVjfBwW0oqgFdhd1Tc9+eyO36imr0PZOjCLYLppbTB8ddb/6D5WtD98P0xwXxIqFLT0KaT6CiskN2QetVBq+MDKqf1Sq4YnGuiRrE8UBVmOAfekZfsZVFClXpoLPVPfCTa+l5WHad3TMuEDFjYiU/wX9jjPXV8xAheCpN+qZb9V6w0quF6hhH22+3e4oUENLd2sJVrotcAh919StKwmq+mYz4b890z76gw5YTdk82i83ZHj9R+x4769bMlATxARC999dxwwqB3EGh8B4O25O5r9SQkLMtgqqmE5fpgYWrPlqitV1zlyMvEZi284B4uIM5xgTJW0jaJmigEVFJ08D4/DGP3mPfNfz+G7bFc/FaNAFyIZjd2Y64208cbPJPCCFMqLtlcMIaKQhnTNs8rielVAxWH63V/FOpw+9Zj8uB6m1EQLAFbgIqmgRdVelk6/lKI8XsI0D+Oe5H+X8GwM5Ts9s5+xAzYSkBQs5k8Gx6FH8M7KBhZIyV8TB5PPTANKfMqt9+mx8BeUTYPGtbdEqsHosuis6+hegM57F5OJ0hL/hGhSXJZbzFYsiiI0OLsYVuexDZ4UoB7cI6cTpIDcL7a0xS1U/qKErr6MFZz6mpJ2x0a1E14Dk1/d6uCOAYkYawwxl3UQpHli7WOk1XU3iS8NJzshixpWb9k4Ac1+bEu6uXjeSXAU24rbMtm6ck9en7y/LcSCuqlH7W22obKcuIGHRwgLSIMvthB/SA+g9BDEJVIEjdfOQeaqJv4WENT+797OWp9888X7MSLf95Ecn+w4dtk7zGgZA65y565QbEVKPDrvE76ed1u0gDY4YOPVWJNMzlE+RBowPSYZz0SiKIQwsqXpuQMWl1Id0+WMLzMtFyBKmAxpAeCEuAUiBiuYjv5Bfv44slCYB2BP3lEcqtZtC5uVf7BiXI+5gFyja3KmzKNjD6uLxXmyg5bjH3113qkRg5SPxFMAzgSXxERu3gru85qSiDExbbV5Ppv2OVJZwqkKbWZgcdv16R3PMbUzzLleIj09p4G3U04U7B4QrIdtR1lb3BsPVYHA3fX12U5f3/sIJSnZ9Nl2YnjX5JpW9achzKq0THDn8xTQhWbUnXyTRt5xXr9ENf+wegFAKYpxoafSDcZnd4veDjaXYFkQJPgHpDib7LjzVRa47j76UWgoEKdYlwJHcsPi6I3ag0vQCFeVdQfdrinwgpr2A5ZgNrc9Cav9tuGiM29sBIEtW80MTT0IjljAzYzsSaFD3pzxvR4phCA53XMphRgFsbT9tTaLiNPfIqFNXCPZ0tBIh3Ur/qHg/fIsUZj6fmiL582Y4ltkEG6VPpXH4mBJXA4GSe50P9IHOWvNZgOUT18IXEFrkSYVZweh9yPvzsvI7tbI36xLYyYiS7kn47kgh75m+8XttDj3wYpKLDkSk832Z3cPsxFXtJqMrDyIftSsVBPoNaBEcv2FSZuuFwQ7PqJ+Rn73f1bFbV2R6iYupYZ0GxH6N8pXk+LkfJM7w="; + public const string StringTestData = "ABC"; public const string TestPassword = "Key123"; diff --git a/wrappers/csharp/tests/unit-tests/TestStreams.cs b/wrappers/csharp/tests/unit-tests/TestStreams.cs new file mode 100644 index 000000000..88d40edd0 --- /dev/null +++ b/wrappers/csharp/tests/unit-tests/TestStreams.cs @@ -0,0 +1,60 @@ +#pragma warning disable SA1600 // Elements should be documented +namespace Devolutions.Crypto.Tests +{ + using System; + using System.IO; + using Devolutions.Cryptography; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestStreams + { + [TestMethod] + public void EncryptStream() + { + byte[] base64DataAsUtf8ByteArray = Utils.Base64StringToByteArray(TestData.Base64TestDataStream); + + using MemoryStream ms = new MemoryStream(); + using (EncryptionStream ec = new EncryptionStream(TestData.BytesTestKey, Array.Empty(), 1000, false, 0, ms)) + { + byte[] header = ec.GetHeader(); + + Assert.IsTrue(Utils.ValidateHeader(header, DataType.OnlineCiphertext)); + + ec.Write(base64DataAsUtf8ByteArray, 0, base64DataAsUtf8ByteArray.Length); + ec.FlushFinalBlock(); + } + + byte[] result = ms.ToArray(); + + Assert.AreNotEqual(result, base64DataAsUtf8ByteArray); + Assert.AreNotEqual(result, new byte[1000]); + Assert.IsTrue(result.Length == 1721); + } + + [TestMethod] + public void DecryptStream() + { + byte[] base64DataAsUtf8ByteArray = Utils.Base64StringToByteArray(TestData.Base64TestDataStreamEncrypted); + + using MemoryStream ms = new MemoryStream(); + + byte[] header = Utils.Base64StringToByteArray((TestData.Base64HeaderDataStreamEncrypted)); + using (DecryptionStream ec = + new DecryptionStream(TestData.BytesTestKey, Array.Empty(), header, false, ms, false)) + { + Assert.IsTrue(Utils.ValidateHeader(header, DataType.OnlineCiphertext)); + + ec.Write(base64DataAsUtf8ByteArray, 0, base64DataAsUtf8ByteArray.Length); + ec.FlushFinalBlock(); + } + + byte[] result = ms.ToArray(); + + Assert.AreNotEqual(result, base64DataAsUtf8ByteArray); + Assert.AreNotEqual(result, new byte[1000]); + Assert.IsTrue(result.Length == 1689); + Assert.IsTrue(Utils.EncodeToBase64String(result) == TestData.Base64TestDataStream); + } + } +} \ No newline at end of file